From 12616e1b75c3b5258c167bf8abf5e7d6ac2437f1 Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Sat, 6 May 2023 10:50:55 +0900 Subject: [PATCH 1/2] Extract Factory Bot cops --- .simplecov | 2 +- CHANGELOG.md | 2 + config/obsoletion.yml | 6 + .../ROOT/pages/cops_rspec_factorybot.adoc | 10 +- lib/rubocop-rspec.rb | 4 +- .../attribute_defined_statically.rb | 143 ++----- .../consistent_parentheses_style.rb | 147 ++----- .../cop/rspec/factory_bot/create_list.rb | 280 ++---------- .../rspec/factory_bot/factory_class_name.rb | 65 +-- .../rspec/factory_bot/factory_name_style.rb | 87 +--- .../cop/rspec/factory_bot/syntax_methods.rb | 124 ++---- lib/rubocop/rspec/config_formatter.rb | 6 + lib/rubocop/rspec/factory_bot.rb | 64 --- lib/rubocop/rspec/factory_bot/language.rb | 37 -- rubocop-rspec.gemspec | 1 + .../attribute_defined_statically_spec.rb | 241 ----------- .../consistent_parentheses_style_spec.rb | 404 ------------------ .../cop/rspec/factory_bot/create_list_spec.rb | 266 ------------ .../factory_bot/factory_class_name_spec.rb | 88 ---- .../factory_bot/factory_name_style_spec.rb | 229 ---------- .../rspec/factory_bot/syntax_methods_spec.rb | 64 --- tasks/cops_documentation.rake | 24 ++ 22 files changed, 228 insertions(+), 2066 deletions(-) delete mode 100644 lib/rubocop/rspec/factory_bot.rb delete mode 100644 lib/rubocop/rspec/factory_bot/language.rb delete mode 100644 spec/rubocop/cop/rspec/factory_bot/attribute_defined_statically_spec.rb delete mode 100644 spec/rubocop/cop/rspec/factory_bot/consistent_parentheses_style_spec.rb delete mode 100644 spec/rubocop/cop/rspec/factory_bot/create_list_spec.rb delete mode 100644 spec/rubocop/cop/rspec/factory_bot/factory_class_name_spec.rb delete mode 100644 spec/rubocop/cop/rspec/factory_bot/factory_name_style_spec.rb delete mode 100644 spec/rubocop/cop/rspec/factory_bot/syntax_methods_spec.rb diff --git a/.simplecov b/.simplecov index cda043f09..75f87802f 100644 --- a/.simplecov +++ b/.simplecov @@ -2,7 +2,7 @@ SimpleCov.start do enable_coverage :branch - minimum_coverage line: 99.60, branch: 95.32 + minimum_coverage line: 99.60, branch: 94.84 add_filter '/spec/' add_filter '/vendor/bundle/' end diff --git a/CHANGELOG.md b/CHANGELOG.md index dcbe25646..6c58ca07d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Extract factory_bot cops to a separate repository, [`rubocop-factory_bot`](https://github.com/rubocop/rubocop-factory_bot). The `rubocop-factory_bot` repository is a dependency of `rubocop-rspec` and the factory_bot cops are aliased (`RSpec/FactoryBot/Foo` == `FactoryBot/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@ydah]) + ## 2.21.0 (2023-05-05) - Fix a false positive in `RSpec/IndexedLet` with suffixes after index-like numbers. ([@pirj]) diff --git a/config/obsoletion.yml b/config/obsoletion.yml index c2f02f8f7..d38b0a973 100644 --- a/config/obsoletion.yml +++ b/config/obsoletion.yml @@ -21,3 +21,9 @@ renamed: RSpec/Capybara/SpecificFinders: Capybara/SpecificFinders RSpec/Capybara/SpecificMatcher: Capybara/SpecificMatcher RSpec/Capybara/VisibilityMatcher: Capybara/VisibilityMatcher + RSpec/FactoryBot/AttributeDefinedStatically: FactoryBot/AttributeDefinedStatically + RSpec/FactoryBot/ConsistentParenthesesStyle: FactoryBot/ConsistentParenthesesStyle + RSpec/FactoryBot/CreateList: FactoryBot/CreateList + RSpec/FactoryBot/FactoryClassName: FactoryBot/FactoryClassName + RSpec/FactoryBot/FactoryNameStyle: FactoryBot/FactoryNameStyle + RSpec/FactoryBot/SyntaxMethods: FactoryBot/SyntaxMethods diff --git a/docs/modules/ROOT/pages/cops_rspec_factorybot.adoc b/docs/modules/ROOT/pages/cops_rspec_factorybot.adoc index 29d03cca7..0e577fca5 100644 --- a/docs/modules/ROOT/pages/cops_rspec_factorybot.adoc +++ b/docs/modules/ROOT/pages/cops_rspec_factorybot.adoc @@ -204,9 +204,9 @@ create_list :user, 3 Use string value when setting the class attribute explicitly. This cop would promote faster tests by lazy-loading of -application files. Also, this could help you suppress potential bugs -in combination with external libraries by avoiding a preload of -application files from the factory files. +application files. Also, this could help you suppress potential +bugs in combination with external libraries by avoiding a preload +of application files from the factory files. === Examples @@ -312,8 +312,8 @@ cannot verify whether you already include `FactoryBot::Syntax::Methods` in your test suite. If you're using Rails, add the following configuration to -`spec/support/factory_bot.rb` and be sure to require that file in -`rails_helper.rb`: +`spec/support/factory_bot.rb` and be sure to require that file +in `rails_helper.rb`: [source,ruby] ---- diff --git a/lib/rubocop-rspec.rb b/lib/rubocop-rspec.rb index 500426939..943600208 100644 --- a/lib/rubocop-rspec.rb +++ b/lib/rubocop-rspec.rb @@ -5,6 +5,7 @@ require 'rubocop' require 'rubocop-capybara' +require 'rubocop-factory_bot' require_relative 'rubocop/rspec' require_relative 'rubocop/rspec/inject' @@ -16,8 +17,6 @@ # Dependent on `RuboCop::RSpec::Language::NodePattern`. require_relative 'rubocop/rspec/language' -require_relative 'rubocop/rspec/factory_bot/language' - require_relative 'rubocop/cop/rspec/mixin/final_end_location' require_relative 'rubocop/cop/rspec/mixin/inside_example_group' require_relative 'rubocop/cop/rspec/mixin/location_help' @@ -37,7 +36,6 @@ require_relative 'rubocop/rspec/corrector/move_node' require_relative 'rubocop/rspec/example' require_relative 'rubocop/rspec/example_group' -require_relative 'rubocop/rspec/factory_bot' require_relative 'rubocop/rspec/hook' RuboCop::RSpec::Inject.defaults! diff --git a/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb b/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb index 92989bdaf..60cbe185c 100644 --- a/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +++ b/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb @@ -4,124 +4,31 @@ module RuboCop module Cop module RSpec module FactoryBot - # Always declare attribute values as blocks. - # - # @example - # # bad - # kind [:active, :rejected].sample - # - # # good - # kind { [:active, :rejected].sample } - # - # # bad - # closed_at 1.day.from_now - # - # # good - # closed_at { 1.day.from_now } - # - # # bad - # count 1 - # - # # good - # count { 1 } - # - class AttributeDefinedStatically < ::RuboCop::Cop::Base - extend AutoCorrector - - MSG = 'Use a block to declare attribute values.' - - # @!method value_matcher(node) - def_node_matcher :value_matcher, <<-PATTERN - (send _ !#reserved_method? $...) - PATTERN - - # @!method factory_attributes(node) - def_node_matcher :factory_attributes, <<-PATTERN - (block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } ) - PATTERN - - def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler - attributes = factory_attributes(node) || [] - attributes = [attributes] unless attributes.is_a?(Array) # rubocop:disable Style/ArrayCoercion, Lint/RedundantCopDisableDirective - - attributes.each do |attribute| - next unless offensive_receiver?(attribute.receiver, node) - next if proc?(attribute) || association?(attribute.first_argument) - - add_offense(attribute) do |corrector| - autocorrect(corrector, attribute) - end - end - end - - private - - def autocorrect(corrector, node) - if node.parenthesized? - autocorrect_replacing_parens(corrector, node) - else - autocorrect_without_parens(corrector, node) - end - end - - def offensive_receiver?(receiver, node) - receiver.nil? || - receiver.self_type? || - receiver_matches_first_block_argument?(receiver, node) - end - - def receiver_matches_first_block_argument?(receiver, node) - first_block_argument = node.arguments.first - - !first_block_argument.nil? && - receiver.lvar_type? && - receiver.node_parts == first_block_argument.node_parts - end - - def proc?(attribute) - value_matcher(attribute).to_a.all?(&:block_pass_type?) - end - - # @!method association?(node) - def_node_matcher :association?, '(hash <(pair (sym :factory) _) ...>)' - - def autocorrect_replacing_parens(corrector, node) - left_braces, right_braces = braces(node) - - corrector.replace(node.location.begin, " #{left_braces}") - corrector.replace(node.location.end, right_braces) - end - - def autocorrect_without_parens(corrector, node) - left_braces, right_braces = braces(node) - - argument = node.first_argument - expression = argument.source_range - corrector.insert_before(expression, left_braces) - corrector.insert_after(expression, right_braces) - end - - def braces(node) - if value_hash_without_braces?(node.first_argument) - ['{ { ', ' } }'] - else - ['{ ', ' }'] - end - end - - def value_hash_without_braces?(node) - node.hash_type? && !node.braces? - end - - def reserved_method?(method_name) - RuboCop::RSpec::FactoryBot.reserved_methods.include?(method_name) - end - - def attribute_defining_method?(method_name) - RuboCop::RSpec::FactoryBot.attribute_defining_methods - .include?(method_name) - end - end + # @!parse + # # Always declare attribute values as blocks. + # # + # # @example + # # # bad + # # kind [:active, :rejected].sample + # # + # # # good + # # kind { [:active, :rejected].sample } + # # + # # # bad + # # closed_at 1.day.from_now + # # + # # # good + # # closed_at { 1.day.from_now } + # # + # # # bad + # # count 1 + # # + # # # good + # # count { 1 } + # # + # class AttributeDefinedStatically < ::RuboCop::Cop::Base; end + AttributeDefinedStatically = + ::RuboCop::Cop::FactoryBot::AttributeDefinedStatically end end end diff --git a/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb b/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb index e015a9973..59089974e 100644 --- a/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +++ b/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb @@ -4,113 +4,46 @@ module RuboCop module Cop module RSpec module FactoryBot - # Use a consistent style for parentheses in factory bot calls. - # - # @example - # - # # bad - # create :user - # build(:user) - # create(:login) - # create :login - # - # @example `EnforcedStyle: require_parentheses` (default) - # - # # good - # create(:user) - # create(:user) - # create(:login) - # build(:login) - # - # @example `EnforcedStyle: omit_parentheses` - # - # # good - # create :user - # build :user - # create :login - # create :login - # - # # also good - # # when method name and first argument are not on same line - # create( - # :user - # ) - # build( - # :user, - # name: 'foo' - # ) - # - class ConsistentParenthesesStyle < ::RuboCop::Cop::Base - extend AutoCorrector - include ConfigurableEnforcedStyle - include RuboCop::RSpec::FactoryBot::Language - include RuboCop::Cop::Util - - def self.autocorrect_incompatible_with - [Style::MethodCallWithArgsParentheses] - end - - MSG_REQUIRE_PARENS = 'Prefer method call with parentheses' - MSG_OMIT_PARENS = 'Prefer method call without parentheses' - - FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS - - RESTRICT_ON_SEND = FACTORY_CALLS - - # @!method factory_call(node) - def_node_matcher :factory_call, <<-PATTERN - (send - {#factory_bot? nil?} %FACTORY_CALLS - {sym str send lvar} _* - ) - PATTERN - - def on_send(node) - return if ambiguous_without_parentheses?(node) - - factory_call(node) do - return if node.method?(:generate) && node.arguments.count > 1 - - if node.parenthesized? - process_with_parentheses(node) - else - process_without_parentheses(node) - end - end - end - - private - - def process_with_parentheses(node) - return unless style == :omit_parentheses - return unless same_line?(node, node.first_argument) - - add_offense(node.loc.selector, - message: MSG_OMIT_PARENS) do |corrector| - remove_parentheses(corrector, node) - end - end - - def process_without_parentheses(node) - return unless style == :require_parentheses - - add_offense(node.loc.selector, - message: MSG_REQUIRE_PARENS) do |corrector| - add_parentheses(node, corrector) - end - end - - AMBIGUOUS_TYPES = %i[send pair array and or if].freeze - - def ambiguous_without_parentheses?(node) - node.parent && AMBIGUOUS_TYPES.include?(node.parent.type) - end - - def remove_parentheses(corrector, node) - corrector.replace(node.location.begin, ' ') - corrector.remove(node.location.end) - end - end + # @!parse + # # Use a consistent style for parentheses in factory bot calls. + # # + # # @example + # # + # # # bad + # # create :user + # # build(:user) + # # create(:login) + # # create :login + # # + # # @example `EnforcedStyle: require_parentheses` (default) + # # + # # # good + # # create(:user) + # # create(:user) + # # create(:login) + # # build(:login) + # # + # # @example `EnforcedStyle: omit_parentheses` + # # + # # # good + # # create :user + # # build :user + # # create :login + # # create :login + # # + # # # also good + # # # when method name and first argument are not on same line + # # create( + # # :user + # # ) + # # build( + # # :user, + # # name: 'foo' + # # ) + # # + # class ConsistentParenthesesStyle < ::RuboCop::Cop::Base; end + ConsistentParenthesesStyle = + ::RuboCop::Cop::FactoryBot::ConsistentParenthesesStyle end end end diff --git a/lib/rubocop/cop/rspec/factory_bot/create_list.rb b/lib/rubocop/cop/rspec/factory_bot/create_list.rb index 996493992..58e5c577d 100644 --- a/lib/rubocop/cop/rspec/factory_bot/create_list.rb +++ b/lib/rubocop/cop/rspec/factory_bot/create_list.rb @@ -4,256 +4,36 @@ module RuboCop module Cop module RSpec module FactoryBot - # Checks for create_list usage. - # - # This cop can be configured using the `EnforcedStyle` option - # - # @example `EnforcedStyle: create_list` (default) - # # bad - # 3.times { create :user } - # - # # good - # create_list :user, 3 - # - # # bad - # 3.times { create :user, age: 18 } - # - # # good - index is used to alter the created models attributes - # 3.times { |n| create :user, age: n } - # - # # good - contains a method call, may return different values - # 3.times { create :user, age: rand } - # - # @example `EnforcedStyle: n_times` - # # bad - # create_list :user, 3 - # - # # good - # 3.times { create :user } - # - class CreateList < ::RuboCop::Cop::Base - extend AutoCorrector - include ConfigurableEnforcedStyle - include RuboCop::RSpec::FactoryBot::Language - - MSG_CREATE_LIST = 'Prefer create_list.' - MSG_N_TIMES = 'Prefer %s.times.' - RESTRICT_ON_SEND = %i[create_list].freeze - - # @!method array_new_or_n_times_block?(node) - def_node_matcher :array_new_or_n_times_block?, <<-PATTERN - (block - { - (send (const {nil? | cbase} :Array) :new (int _)) | - (send (int _) :times) - } - ... - ) - PATTERN - - # @!method block_with_arg_and_used?(node) - def_node_matcher :block_with_arg_and_used?, <<-PATTERN - (block - _ - (args (arg _value)) - `_value - ) - PATTERN - - # @!method arguments_include_method_call?(node) - def_node_matcher :arguments_include_method_call?, <<-PATTERN - (send ${nil? #factory_bot?} :create (sym $_) `$(send ...)) - PATTERN - - # @!method factory_call(node) - def_node_matcher :factory_call, <<-PATTERN - (send ${nil? #factory_bot?} :create (sym $_) $...) - PATTERN - - # @!method factory_list_call(node) - def_node_matcher :factory_list_call, <<-PATTERN - (send {nil? #factory_bot?} :create_list (sym _) (int $_) ...) - PATTERN - - def on_block(node) # rubocop:todo InternalAffairs/NumblockHandler - return unless style == :create_list - - return unless array_new_or_n_times_block?(node) - return if block_with_arg_and_used?(node) - return unless node.body - return if arguments_include_method_call?(node.body) - return unless contains_only_factory?(node.body) - - add_offense(node.send_node, message: MSG_CREATE_LIST) do |corrector| - CreateListCorrector.new(node.send_node).call(corrector) - end - end - - def on_send(node) - return unless style == :n_times - - factory_list_call(node) do |count| - message = format(MSG_N_TIMES, number: count) - add_offense(node.loc.selector, message: message) do |corrector| - TimesCorrector.new(node).call(corrector) - end - end - end - - private - - def contains_only_factory?(node) - if node.block_type? - factory_call(node.send_node) - else - factory_call(node) - end - end - - # :nodoc - module Corrector - private - - def build_options_string(options) - options.map(&:source).join(', ') - end - - def format_method_call(node, method, arguments) - if node.block_type? || node.parenthesized? - "#{method}(#{arguments})" - else - "#{method} #{arguments}" - end - end - - def format_receiver(receiver) - return '' unless receiver - - "#{receiver.source}." - end - end - - # :nodoc - class TimesCorrector - include Corrector - - def initialize(node) - @node = node - end - - def call(corrector) - replacement = generate_n_times_block(node) - corrector.replace(node.block_node || node, replacement) - end - - private - - attr_reader :node - - def generate_n_times_block(node) - factory, count, *options = node.arguments - - arguments = factory.source - options = build_options_string(options) - arguments += ", #{options}" unless options.empty? - - replacement = format_receiver(node.receiver) - replacement += format_method_call(node, 'create', arguments) - replacement += " #{factory_call_block_source}" if node.block_node - "#{count.source}.times { #{replacement} }" - end - - def factory_call_block_source - node.block_node.location.begin.with( - end_pos: node.block_node.location.end.end_pos - ).source - end - end - - # :nodoc: - class CreateListCorrector - include Corrector - - def initialize(node) - @node = node.parent - end - - def call(corrector) - replacement = if node.body.block_type? - call_with_block_replacement(node) - else - call_replacement(node) - end - - corrector.replace(node, replacement) - end - - private - - attr_reader :node - - def call_with_block_replacement(node) - block = node.body - arguments = build_arguments(block, count_from(node)) - replacement = format_receiver(block.receiver) - replacement += format_method_call(block, 'create_list', arguments) - replacement += format_block(block) - replacement - end - - def build_arguments(node, count) - factory, *options = *node.send_node.arguments - - arguments = ":#{factory.value}, #{count}" - options = build_options_string(options) - arguments += ", #{options}" unless options.empty? - arguments - end - - def call_replacement(node) - block = node.body - factory, *options = *block.arguments - - arguments = "#{factory.source}, #{count_from(node)}" - options = build_options_string(options) - arguments += ", #{options}" unless options.empty? - - replacement = format_receiver(block.receiver) - replacement += format_method_call(block, 'create_list', arguments) - replacement - end - - def count_from(node) - count_node = - if node.receiver.int_type? - node.receiver - else - node.send_node.first_argument - end - count_node.source - end - - def format_block(node) - if node.body.begin_type? - format_multiline_block(node) - else - format_singleline_block(node) - end - end - - def format_multiline_block(node) - indent = ' ' * node.body.loc.column - indent_end = ' ' * node.parent.loc.column - " do #{node.arguments.source}\n" \ - "#{indent}#{node.body.source}\n" \ - "#{indent_end}end" - end - - def format_singleline_block(node) - " { #{node.arguments.source} #{node.body.source} }" - end - end - end + # @!parse + # # Checks for create_list usage. + # # + # # This cop can be configured using the `EnforcedStyle` option + # # + # # @example `EnforcedStyle: create_list` (default) + # # # bad + # # 3.times { create :user } + # # + # # # good + # # create_list :user, 3 + # # + # # # bad + # # 3.times { create :user, age: 18 } + # # + # # # good - index is used to alter the created models attributes + # # 3.times { |n| create :user, age: n } + # # + # # # good - contains a method call, may return different values + # # 3.times { create :user, age: rand } + # # + # # @example `EnforcedStyle: n_times` + # # # bad + # # create_list :user, 3 + # # + # # # good + # # 3.times { create :user } + # # + # class CreateList < ::RuboCop::Cop::Base; end + CreateList = ::RuboCop::Cop::FactoryBot::CreateList end end end diff --git a/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb b/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb index 0ac4a4514..cb0f5a970 100644 --- a/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +++ b/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb @@ -4,52 +4,25 @@ module RuboCop module Cop module RSpec module FactoryBot - # Use string value when setting the class attribute explicitly. - # - # This cop would promote faster tests by lazy-loading of - # application files. Also, this could help you suppress potential bugs - # in combination with external libraries by avoiding a preload of - # application files from the factory files. - # - # @example - # # bad - # factory :foo, class: Foo do - # end - # - # # good - # factory :foo, class: 'Foo' do - # end - # - class FactoryClassName < ::RuboCop::Cop::Base - extend AutoCorrector - - MSG = "Pass '%s' string instead of `%s` " \ - 'constant.' - ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze - RESTRICT_ON_SEND = %i[factory].freeze - - # @!method class_name(node) - def_node_matcher :class_name, <<~PATTERN - (send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>)) - PATTERN - - def on_send(node) - class_name(node) do |cn| - next if allowed?(cn.const_name) - - msg = format(MSG, class_name: cn.const_name) - add_offense(cn, message: msg) do |corrector| - corrector.replace(cn, "'#{cn.source}'") - end - end - end - - private - - def allowed?(const_name) - ALLOWED_CONSTANTS.include?(const_name) - end - end + # @!parse + # # Use string value when setting the class attribute explicitly. + # # + # # This cop would promote faster tests by lazy-loading of + # # application files. Also, this could help you suppress potential + # # bugs in combination with external libraries by avoiding a preload + # # of application files from the factory files. + # # + # # @example + # # # bad + # # factory :foo, class: Foo do + # # end + # # + # # # good + # # factory :foo, class: 'Foo' do + # # end + # # + # class FactoryClassName < ::RuboCop::Cop::Base; end + FactoryClassName = ::RuboCop::Cop::FactoryBot::FactoryClassName end end end diff --git a/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb b/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb index 570873496..c04ed4702 100644 --- a/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +++ b/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb @@ -4,70 +4,29 @@ module RuboCop module Cop module RSpec module FactoryBot - # Checks for name style for argument of FactoryBot::Syntax::Methods. - # - # @example EnforcedStyle: symbol (default) - # # bad - # create('user') - # build "user", username: "NAME" - # - # # good - # create(:user) - # build :user, username: "NAME" - # - # @example EnforcedStyle: string - # # bad - # create(:user) - # build :user, username: "NAME" - # - # # good - # create('user') - # build "user", username: "NAME" - # - class FactoryNameStyle < ::RuboCop::Cop::Base - extend AutoCorrector - include ConfigurableEnforcedStyle - include RuboCop::RSpec::FactoryBot::Language - - MSG = 'Use %s to refer to a factory.' - FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS - RESTRICT_ON_SEND = FACTORY_CALLS - - # @!method factory_call(node) - def_node_matcher :factory_call, <<-PATTERN - (send - {#factory_bot? nil?} %FACTORY_CALLS - ${str sym} ... - ) - PATTERN - - def on_send(node) - factory_call(node) do |name| - if offense_for_symbol_style?(name) - register_offense(name, name.value.to_sym.inspect) - elsif offense_for_string_style?(name) - register_offense(name, name.value.to_s.inspect) - end - end - end - - private - - def offense_for_symbol_style?(name) - name.str_type? && style == :symbol - end - - def offense_for_string_style?(name) - name.sym_type? && style == :string - end - - def register_offense(name, prefer) - add_offense(name, - message: format(MSG, prefer: style.to_s)) do |corrector| - corrector.replace(name, prefer) - end - end - end + # @!parse + # # Checks for name style for argument of FactoryBot::Syntax::Methods. + # # + # # @example EnforcedStyle: symbol (default) + # # # bad + # # create('user') + # # build "user", username: "NAME" + # # + # # # good + # # create(:user) + # # build :user, username: "NAME" + # # + # # @example EnforcedStyle: string + # # # bad + # # create(:user) + # # build :user, username: "NAME" + # # + # # # good + # # create('user') + # # build "user", username: "NAME" + # # + # class FactoryNameStyle < ::RuboCop::Cop::Base; end + FactoryNameStyle = ::RuboCop::Cop::FactoryBot::FactoryNameStyle end end end diff --git a/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb b/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb index 78fdd1bf1..05492740f 100644 --- a/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +++ b/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb @@ -4,85 +4,51 @@ module RuboCop module Cop module RSpec module FactoryBot - # Use shorthands from `FactoryBot::Syntax::Methods` in your specs. - # - # @safety - # The autocorrection is marked as unsafe because the cop - # cannot verify whether you already include - # `FactoryBot::Syntax::Methods` in your test suite. - # - # If you're using Rails, add the following configuration to - # `spec/support/factory_bot.rb` and be sure to require that file in - # `rails_helper.rb`: - # - # [source,ruby] - # ---- - # RSpec.configure do |config| - # config.include FactoryBot::Syntax::Methods - # end - # ---- - # - # If you're not using Rails: - # - # [source,ruby] - # ---- - # RSpec.configure do |config| - # config.include FactoryBot::Syntax::Methods - # - # config.before(:suite) do - # FactoryBot.find_definitions - # end - # end - # ---- - # - # @example - # # bad - # FactoryBot.create(:bar) - # FactoryBot.build(:bar) - # FactoryBot.attributes_for(:bar) - # - # # good - # create(:bar) - # build(:bar) - # attributes_for(:bar) - # - class SyntaxMethods < Base - extend AutoCorrector - include InsideExampleGroup - include RangeHelp - include RuboCop::RSpec::FactoryBot::Language - - MSG = 'Use `%s` from `FactoryBot::Syntax::Methods`.' - - RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS - - def on_send(node) - return unless factory_bot?(node.receiver) - return unless inside_example_group?(node) - - message = format(MSG, method: node.method_name) - - add_offense(crime_scene(node), message: message) do |corrector| - corrector.remove(offense(node)) - end - end - - private - - def crime_scene(node) - range_between( - node.source_range.begin_pos, - node.loc.selector.end_pos - ) - end - - def offense(node) - range_between( - node.source_range.begin_pos, - node.loc.selector.begin_pos - ) - end - end + # @!parse + # # Use shorthands from `FactoryBot::Syntax::Methods` in your specs. + # # + # # @safety + # # The autocorrection is marked as unsafe because the cop + # # cannot verify whether you already include + # # `FactoryBot::Syntax::Methods` in your test suite. + # # + # # If you're using Rails, add the following configuration to + # # `spec/support/factory_bot.rb` and be sure to require that file + # # in `rails_helper.rb`: + # # + # # [source,ruby] + # # ---- + # # RSpec.configure do |config| + # # config.include FactoryBot::Syntax::Methods + # # end + # # ---- + # # + # # If you're not using Rails: + # # + # # [source,ruby] + # # ---- + # # RSpec.configure do |config| + # # config.include FactoryBot::Syntax::Methods + # # + # # config.before(:suite) do + # # FactoryBot.find_definitions + # # end + # # end + # # ---- + # # + # # @example + # # # bad + # # FactoryBot.create(:bar) + # # FactoryBot.build(:bar) + # # FactoryBot.attributes_for(:bar) + # # + # # # good + # # create(:bar) + # # build(:bar) + # # attributes_for(:bar) + # # + # class SyntaxMethods < ::RuboCop::Cop::Base; end + SyntaxMethods = ::RuboCop::Cop::FactoryBot::SyntaxMethods end end end diff --git a/lib/rubocop/rspec/config_formatter.rb b/lib/rubocop/rspec/config_formatter.rb index 2d7553c86..f41985e42 100644 --- a/lib/rubocop/rspec/config_formatter.rb +++ b/lib/rubocop/rspec/config_formatter.rb @@ -16,6 +16,12 @@ class ConfigFormatter RSpec/Capybara/SpecificFinders RSpec/Capybara/SpecificMatcher RSpec/Capybara/VisibilityMatcher + RSpec/FactoryBot/AttributeDefinedStatically + RSpec/FactoryBot/ConsistentParenthesesStyle + RSpec/FactoryBot/CreateList + RSpec/FactoryBot/FactoryClassName + RSpec/FactoryBot/FactoryNameStyle + RSpec/FactoryBot/SyntaxMethods ) AMENDMENTS = %(Metrics/BlockLength) COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/' diff --git a/lib/rubocop/rspec/factory_bot.rb b/lib/rubocop/rspec/factory_bot.rb deleted file mode 100644 index 9fbda437f..000000000 --- a/lib/rubocop/rspec/factory_bot.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module RSpec - # RuboCop FactoryBot project namespace - module FactoryBot - ATTRIBUTE_DEFINING_METHODS = %i[ - factory - ignore - trait - traits_for_enum - transient - ].freeze - - UNPROXIED_METHODS = %i[ - __send__ - __id__ - nil? - send - object_id - extend - instance_eval - initialize - block_given? - raise - caller - method - ].freeze - - DEFINITION_PROXY_METHODS = %i[ - add_attribute - after - association - before - callback - ignore - initialize_with - sequence - skip_create - to_create - ].freeze - - RESERVED_METHODS = - DEFINITION_PROXY_METHODS + - UNPROXIED_METHODS + - ATTRIBUTE_DEFINING_METHODS - - private_constant( - :ATTRIBUTE_DEFINING_METHODS, - :UNPROXIED_METHODS, - :DEFINITION_PROXY_METHODS, - :RESERVED_METHODS - ) - - def self.attribute_defining_methods - ATTRIBUTE_DEFINING_METHODS - end - - def self.reserved_methods - RESERVED_METHODS - end - end - end -end diff --git a/lib/rubocop/rspec/factory_bot/language.rb b/lib/rubocop/rspec/factory_bot/language.rb deleted file mode 100644 index 904eb261f..000000000 --- a/lib/rubocop/rspec/factory_bot/language.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module RSpec - module FactoryBot - # Contains node matchers for common FactoryBot DSL. - module Language - extend RuboCop::NodePattern::Macros - - METHODS = %i[ - attributes_for - attributes_for_list - attributes_for_pair - build - build_list - build_pair - build_stubbed - build_stubbed_list - build_stubbed_pair - create - create_list - create_pair - generate - generate_list - null - null_list - null_pair - ].to_set.freeze - - # @!method factory_bot?(node) - def_node_matcher :factory_bot?, <<~PATTERN - (const {nil? cbase} {:FactoryGirl :FactoryBot}) - PATTERN - end - end - end -end diff --git a/rubocop-rspec.gemspec b/rubocop-rspec.gemspec index 5b88306bd..bd5f113fb 100644 --- a/rubocop-rspec.gemspec +++ b/rubocop-rspec.gemspec @@ -39,4 +39,5 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'rubocop', '~> 1.33' spec.add_runtime_dependency 'rubocop-capybara', '~> 2.17' + spec.add_runtime_dependency 'rubocop-factory_bot', '~> 2.22' end diff --git a/spec/rubocop/cop/rspec/factory_bot/attribute_defined_statically_spec.rb b/spec/rubocop/cop/rspec/factory_bot/attribute_defined_statically_spec.rb deleted file mode 100644 index e14ef30bc..000000000 --- a/spec/rubocop/cop/rspec/factory_bot/attribute_defined_statically_spec.rb +++ /dev/null @@ -1,241 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Cop::RSpec::FactoryBot::AttributeDefinedStatically do - def inspected_source_filename - 'spec/factories.rb' - end - - it 'registers an offense for offending code' do - expect_offense(<<-RUBY) - FactoryBot.define do - factory :post do - title "Something" - ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - comments_count 0 - ^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - tag Tag::MAGIC - ^^^^^^^^^^^^^^ Use a block to declare attribute values. - recent_statuses [] - ^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - status([:draft, :published].sample) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - published_at 1.day.from_now - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - created_at(1.day.ago) - ^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - update_times [Time.current] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - meta_tags(foo: Time.current) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - other_tags({ foo: Time.current }) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - options color: :blue - ^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - other_options Tag::MAGIC => :magic - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - end - end - RUBY - - expect_correction(<<-RUBY) - FactoryBot.define do - factory :post do - title { "Something" } - comments_count { 0 } - tag { Tag::MAGIC } - recent_statuses { [] } - status { [:draft, :published].sample } - published_at { 1.day.from_now } - created_at { 1.day.ago } - update_times { [Time.current] } - meta_tags { { foo: Time.current } } - other_tags { { foo: Time.current } } - options { { color: :blue } } - other_options { { Tag::MAGIC => :magic } } - end - end - RUBY - end - - it 'registers an offense in a trait' do - expect_offense(<<-RUBY) - FactoryBot.define do - factory :post do - trait :published do - title "Something" - ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - published_at 1.day.from_now - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - end - end - end - RUBY - - expect_correction(<<-RUBY) - FactoryBot.define do - factory :post do - trait :published do - title { "Something" } - published_at { 1.day.from_now } - end - end - end - RUBY - end - - it 'registers an offense in a transient block' do - expect_offense(<<-RUBY) - FactoryBot.define do - factory :post do - transient do - title "Something" - ^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - published_at 1.day.from_now - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - end - end - end - RUBY - - expect_correction(<<-RUBY) - FactoryBot.define do - factory :post do - transient do - title { "Something" } - published_at { 1.day.from_now } - end - end - end - RUBY - end - - it 'registers an offense for an attribute defined on `self`' do - expect_offense(<<-RUBY) - FactoryBot.define do - factory :post do - self.start { Date.today } - self.end Date.tomorrow - ^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - end - end - RUBY - - expect_correction(<<-RUBY) - FactoryBot.define do - factory :post do - self.start { Date.today } - self.end { Date.tomorrow } - end - end - RUBY - end - - it 'registers an offense for attributes defined on explicit receiver' do - expect_offense(<<-RUBY) - FactoryBot.define do - factory :post do |post_definition| - post_definition.end Date.tomorrow - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - post_definition.trait :published do |published_definition| - published_definition.published_at 1.day.from_now - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use a block to declare attribute values. - end - end - end - RUBY - - expect_correction(<<-RUBY) - FactoryBot.define do - factory :post do |post_definition| - post_definition.end { Date.tomorrow } - post_definition.trait :published do |published_definition| - published_definition.published_at { 1.day.from_now } - end - end - end - RUBY - end - - it 'accepts valid factory definitions' do - expect_no_offenses(<<-RUBY) - FactoryBot.define do - factory :post do - trait :published do - published_at { 1.day.from_now } - end - created_at { 1.day.ago } - status { :draft } - comments_count { 0 } - title { "Static" } - description { FFaker::Lorem.paragraph(10) } - recent_statuses { [] } - tags { { like_count: 2 } } - - before(:create, &:initialize_something) - after(:create, &:rebuild_cache) - end - end - RUBY - end - - it 'does not add offense if out of factory bot block' do - expect_no_offenses(<<-RUBY) - status [:draft, :published].sample - published_at 1.day.from_now - created_at 1.day.ago - update_times [Time.current] - meta_tags(foo: Time.current) - RUBY - end - - it 'does not add offense if method called on another object' do - expect_no_offenses(<<-RUBY) - FactoryBot.define do - factory :post do |post_definition| - Registrar.register :post_factory - end - end - RUBY - end - - it 'does not add offense if method called on a local variable' do - expect_no_offenses(<<-RUBY) - FactoryBot.define do - factory :post do |post_definition| - local = Registrar - local.register :post_factory - end - end - RUBY - end - - it 'accepts valid association definitions' do - expect_no_offenses(<<-RUBY) - FactoryBot.define do - factory :post do - author age: 42, factory: :user - end - end - RUBY - end - - it 'accepts valid sequence definition' do - expect_no_offenses(<<-RUBY) - FactoryBot.define do - factory :post do - sequence :negative_numbers, &:-@ - end - end - RUBY - end - - it 'accepts valid traits_for_enum definition' do - expect_no_offenses(<<-RUBY) - FactoryBot.define do - factory :post do - traits_for_enum :status, [:draft, :published] - end - end - RUBY - end -end diff --git a/spec/rubocop/cop/rspec/factory_bot/consistent_parentheses_style_spec.rb b/spec/rubocop/cop/rspec/factory_bot/consistent_parentheses_style_spec.rb deleted file mode 100644 index 44dcbf76a..000000000 --- a/spec/rubocop/cop/rspec/factory_bot/consistent_parentheses_style_spec.rb +++ /dev/null @@ -1,404 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle do - let(:cop_config) do - { 'EnforcedStyle' => enforced_style } - end - - context 'when EnforcedStyle is :enforce_parentheses' do - let(:enforced_style) { :require_parentheses } - - context 'with create' do - it 'flags the call to use parentheses' do - expect_offense(<<~RUBY) - create :user - ^^^^^^ Prefer method call with parentheses - RUBY - - expect_correction(<<~RUBY) - create(:user) - RUBY - end - end - - context 'with multiline method calls' do - it 'expects parentheses around multiline call' do - expect_offense(<<~RUBY) - create :user, - ^^^^^^ Prefer method call with parentheses - username: "PETER", - peter: "USERNAME" - RUBY - - expect_correction(<<~RUBY) - create(:user, - username: "PETER", - peter: "USERNAME") - RUBY - end - end - - context 'with build' do - it 'flags the call to use parentheses' do - expect_offense(<<~RUBY) - build :user - ^^^^^ Prefer method call with parentheses - RUBY - - expect_correction(<<~RUBY) - build(:user) - RUBY - end - end - - context 'with mixed tests' do - it 'flags the call to use parentheses' do - expect_offense(<<~RUBY) - build_list :user, 10 - ^^^^^^^^^^ Prefer method call with parentheses - build_list "user", 10 - ^^^^^^^^^^ Prefer method call with parentheses - create_list :user, 10 - ^^^^^^^^^^^ Prefer method call with parentheses - build_stubbed :user - ^^^^^^^^^^^^^ Prefer method call with parentheses - build_stubbed_list :user, 10 - ^^^^^^^^^^^^^^^^^^ Prefer method call with parentheses - build factory - ^^^^^ Prefer method call with parentheses - RUBY - - expect_correction(<<~RUBY) - build_list(:user, 10) - build_list("user", 10) - create_list(:user, 10) - build_stubbed(:user) - build_stubbed_list(:user, 10) - build(factory) - RUBY - end - end - - context 'with nested calling' do - it 'flags the call to use parentheses' do - expect_offense(<<~RUBY) - build :user, build(:yester) - ^^^^^ Prefer method call with parentheses - RUBY - - expect_correction(<<~RUBY) - build(:user, build(:yester)) - RUBY - end - - it 'works in a bigger context' do - expect_offense(<<~RUBY) - context 'with context' do - let(:build) { create :user, build(:user) } - ^^^^^^ Prefer method call with parentheses - - it 'test in test' do - user = create :user, first: name, peter: miller - ^^^^^^ Prefer method call with parentheses - end - - let(:build) { create :user, build(:user, create(:user, create(:first_name))) } - ^^^^^^ Prefer method call with parentheses - end - RUBY - - expect_correction(<<~RUBY) - context 'with context' do - let(:build) { create(:user, build(:user)) } - - it 'test in test' do - user = create(:user, first: name, peter: miller) - end - - let(:build) { create(:user, build(:user, create(:user, create(:first_name)))) } - end - RUBY - end - end - - context 'with already valid usage of parentheses' do - it 'does not flag as invalid - create' do - expect_no_offenses(<<~RUBY) - create(:user) - RUBY - end - - it 'does not flag as invalid - build' do - expect_no_offenses(<<~RUBY) - build(:user) - RUBY - end - end - - it 'flags the call with an explicit receiver' do - expect_offense(<<~RUBY) - FactoryBot.create :user - ^^^^^^ Prefer method call with parentheses - RUBY - end - - it 'ignores FactoryBot DSL methods without a first positional argument' do - expect_no_offenses(<<~RUBY) - create - create foo: :bar - RUBY - end - - it 'dose not register an offense when using `generate` ' \ - 'with not a one argument' do - expect_no_offenses(<<~RUBY) - generate - generate :foo, :bar - RUBY - end - end - - context 'when EnforcedStyle is :omit_parentheses' do - let(:enforced_style) { :omit_parentheses } - - context 'with create' do - it 'flags the call to not use parentheses' do - expect_offense(<<~RUBY) - create(:user) - ^^^^^^ Prefer method call without parentheses - RUBY - - expect_correction(<<~RUBY) - create :user - RUBY - end - end - - context 'with nest call' do - it 'inner call is ignored and not fixed' do - expect_no_offenses(<<~RUBY) - puts(1, create(:user)) - RUBY - end - end - - context 'with multiline method calls' do - it 'removes parentheses around multiline call' do - expect_offense(<<~RUBY) - create(:user, - ^^^^^^ Prefer method call without parentheses - username: "PETER", - peter: "USERNAME") - RUBY - - expect_correction(<<~RUBY) - create :user, - username: "PETER", - peter: "USERNAME" - RUBY - end - end - - %w[&& ||].each do |operator| - context "with #{operator}" do - it 'does not flag the call' do - expect_no_offenses(<<~RUBY) - can_create_user? #{operator} create(:user) - RUBY - end - end - end - - context 'with ternary operator' do - it 'does not flag the call' do - expect_no_offenses(<<~RUBY) - can_create_user? ? create(:user) : nil - RUBY - end - end - - context 'with mixed tests' do - it 'flags the call not to use parentheses' do - expect_offense(<<~RUBY) - build_list(:user, 10) - ^^^^^^^^^^ Prefer method call without parentheses - build_list("user", 10) - ^^^^^^^^^^ Prefer method call without parentheses - create_list(:user, 10) - ^^^^^^^^^^^ Prefer method call without parentheses - build_stubbed(:user) - ^^^^^^^^^^^^^ Prefer method call without parentheses - build_stubbed_list(:user, 10) - ^^^^^^^^^^^^^^^^^^ Prefer method call without parentheses - build(factory) - ^^^^^ Prefer method call without parentheses - RUBY - - expect_correction(<<~RUBY) - build_list :user, 10 - build_list "user", 10 - create_list :user, 10 - build_stubbed :user - build_stubbed_list :user, 10 - build factory - RUBY - end - end - - context 'with build' do - it 'flags the call to not use parentheses' do - expect_offense(<<~RUBY) - build(:user) - ^^^^^ Prefer method call without parentheses - RUBY - - expect_correction(<<~RUBY) - build :user - RUBY - end - end - - context 'with nested calling' do - it 'flags the call to use parentheses' do - expect_offense(<<~RUBY) - build(:user, build(:yester)) - ^^^^^ Prefer method call without parentheses - RUBY - - expect_correction(<<~RUBY) - build :user, build(:yester) - RUBY - end - end - - context 'with nested calling that does not require fixing' do - it 'does not flag the nested call' do - expect_no_offenses(<<~RUBY) - build :user, build(:yester) - RUBY - end - end - - context 'when is a part of a hash' do - it 'does not flag the call' do - expect_no_offenses(<<~RUBY) - build :user, home: build(:address) - RUBY - end - end - - context 'when is a part of an array' do - it 'does not flag the call' do - expect_no_offenses(<<~RUBY) - users = [ - build(:user), - build(:user) - ] - RUBY - end - end - - context 'with already valid usage of parentheses' do - it 'does not flag as invalid - create' do - expect_no_offenses(<<~RUBY) - create :user - RUBY - end - - it 'does not flag as invalid - build' do - expect_no_offenses(<<~RUBY) - build :user - RUBY - end - end - - it 'works in a bigger context' do - expect_offense(<<~RUBY) - RSpec.describe Context do - let(:build) { create(:user, build(:user)) } - ^^^^^^ Prefer method call without parentheses - - it 'test in test' do - user = create(:user, first: name, peter: miller) - ^^^^^^ Prefer method call without parentheses - end - - let(:build) { create(:user, build(:user, create(:user, create(:first_name)))) } - ^^^^^^ Prefer method call without parentheses - end - RUBY - - expect_correction(<<~RUBY) - RSpec.describe Context do - let(:build) { create :user, build(:user) } - - it 'test in test' do - user = create :user, first: name, peter: miller - end - - let(:build) { create :user, build(:user, create(:user, create(:first_name))) } - end - RUBY - end - - context 'when create and first argument are on same line' do - it 'register an offense' do - expect_offense(<<~RUBY) - create(:user, - ^^^^^^ Prefer method call without parentheses - name: 'foo' - ) - RUBY - - expect_correction(<<~RUBY) - create :user, - name: 'foo' - - RUBY - end - end - - context 'when create and first argument are not on same line' do - it 'does not register an offense' do - expect_no_offenses(<<~RUBY) - create( - :user - ) - RUBY - end - end - - context 'when create and some argument are not on same line' do - it 'does not register an offense' do - expect_no_offenses(<<~RUBY) - create( - :user, - name: 'foo' - ) - RUBY - end - end - - it 'flags the call with an explicit receiver' do - expect_offense(<<~RUBY) - FactoryBot.create(:user) - ^^^^^^ Prefer method call without parentheses - RUBY - end - - it 'ignores FactoryBot DSL methods without a first positional argument' do - expect_no_offenses(<<~RUBY) - create() - create(foo: :bar) - RUBY - end - - it 'dose not register an offense when using `generate` ' \ - 'with not a one argument' do - expect_no_offenses(<<~RUBY) - generate() - generate(:foo, :bar) - RUBY - end - end -end diff --git a/spec/rubocop/cop/rspec/factory_bot/create_list_spec.rb b/spec/rubocop/cop/rspec/factory_bot/create_list_spec.rb deleted file mode 100644 index 5f63b2959..000000000 --- a/spec/rubocop/cop/rspec/factory_bot/create_list_spec.rb +++ /dev/null @@ -1,266 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Cop::RSpec::FactoryBot::CreateList do - let(:cop_config) do - { 'EnforcedStyle' => enforced_style } - end - - context 'when EnforcedStyle is :create_list' do - let(:enforced_style) { :create_list } - - it 'flags usage of n.times with no arguments' do - expect_offense(<<~RUBY) - 3.times { create :user } - ^^^^^^^ Prefer create_list. - RUBY - - expect_correction(<<~RUBY) - create_list :user, 3 - RUBY - end - - it 'flags usage of n.times when FactoryGirl.create is used' do - expect_offense(<<~RUBY) - 3.times { FactoryGirl.create :user } - ^^^^^^^ Prefer create_list. - RUBY - - expect_correction(<<~RUBY) - FactoryGirl.create_list :user, 3 - RUBY - end - - it 'flags usage of n.times when FactoryBot.create is used' do - expect_offense(<<~RUBY) - 3.times { FactoryBot.create :user } - ^^^^^^^ Prefer create_list. - RUBY - - expect_correction(<<~RUBY) - FactoryBot.create_list :user, 3 - RUBY - end - - it 'ignores create method of other object' do - expect_no_offenses(<<~RUBY) - 3.times { SomeFactory.create :user } - RUBY - end - - it 'ignores create in other block' do - expect_no_offenses(<<~RUBY) - allow(User).to receive(:create) { create :user } - RUBY - end - - it 'ignores n.times with n as argument' do - expect_no_offenses(<<~RUBY) - 3.times { |n| create :user, position: n } - RUBY - end - - it 'flags n.times when create call doesn\'t have method calls' do - expect_offense(<<~RUBY) - 3.times { |n| create :user, :active } - ^^^^^^^ Prefer create_list. - 3.times { |n| create :user, password: '123' } - ^^^^^^^ Prefer create_list. - 3.times { |n| create :user, :active, password: '123' } - ^^^^^^^ Prefer create_list. - RUBY - end - - it 'ignores n.times when create call does have method calls' do - expect_no_offenses(<<~RUBY) - 3.times { |n| create :user, repositories_count: rand } - RUBY - end - - it 'ignores n.times when there is no create call inside' do - expect_no_offenses(<<~RUBY) - 3.times { do_something } - RUBY - end - - it 'ignores empty n.times' do - expect_no_offenses(<<~RUBY) - 3.times {} - RUBY - end - - it 'ignores n.times when there is other calls but create' do - expect_no_offenses(<<~RUBY) - used_passwords = [] - 3.times do - u = create :user - expect(used_passwords).not_to include(u.password) - used_passwords << u.password - end - RUBY - end - - it 'flags FactoryGirl.create calls with a block' do - expect_offense(<<~RUBY) - 3.times do - ^^^^^^^ Prefer create_list. - create(:user) { |user| create :account, user: user } - end - RUBY - - expect_correction(<<~RUBY) - create_list(:user, 3) { |user| create :account, user: user } - RUBY - end - - it 'flags usage of n.times with arguments' do - expect_offense(<<~RUBY) - 5.times { create(:user, :trait) } - ^^^^^^^ Prefer create_list. - RUBY - - expect_correction(<<~RUBY) - create_list(:user, 5, :trait) - RUBY - end - - it 'flags usage of n.times with keyword arguments' do - expect_offense(<<~RUBY) - 5.times { create :user, :trait, key: val } - ^^^^^^^ Prefer create_list. - RUBY - - expect_correction(<<~RUBY) - create_list :user, 5, :trait, key: val - RUBY - end - - it 'flags usage of n.times with block argument' do - expect_offense(<<~RUBY) - 3.times do - ^^^^^^^ Prefer create_list. - create(:user, :trait) { |user| create :account, user: user } - end - RUBY - - expect_correction(<<~RUBY) - create_list(:user, 3, :trait) { |user| create :account, user: user } - RUBY - end - - it 'flags usage of n.times with nested block arguments' do - expect_offense(<<~RUBY) - 3.times do - ^^^^^^^ Prefer create_list. - create(:user, :trait) do |user| - create :account, user: user - create :profile, user: user - end - end - RUBY - - expect_correction(<<~RUBY) - create_list(:user, 3, :trait) do |user| - create :account, user: user - create :profile, user: user - end - RUBY - end - - it 'flags usage of Array.new(n) with no arguments' do - expect_offense(<<~RUBY) - Array.new(3) { create(:user) } - ^^^^^^^^^^^^ Prefer create_list. - RUBY - - expect_correction(<<~RUBY) - create_list(:user, 3) - RUBY - end - - it 'flags usage of Array.new(n) with block argument' do - expect_offense(<<~RUBY) - Array.new(3) do - ^^^^^^^^^^^^ Prefer create_list. - create(:user) { |user| create(:account, user: user) } - end - RUBY - - expect_correction(<<~RUBY) - create_list(:user, 3) { |user| create(:account, user: user) } - RUBY - end - end - - context 'when EnforcedStyle is :n_times' do - let(:enforced_style) { :n_times } - - it 'flags usage of create_list' do - expect_offense(<<~RUBY) - create_list :user, 3 - ^^^^^^^^^^^ Prefer 3.times. - RUBY - - expect_correction(<<~RUBY) - 3.times { create :user } - RUBY - end - - it 'flags usage of create_list with argument' do - expect_offense(<<~RUBY) - create_list(:user, 3, :trait) - ^^^^^^^^^^^ Prefer 3.times. - RUBY - - expect_correction(<<~RUBY) - 3.times { create(:user, :trait) } - RUBY - end - - it 'flags usage of create_list with keyword arguments' do - expect_offense(<<~RUBY) - create_list :user, 3, :trait, key: val - ^^^^^^^^^^^ Prefer 3.times. - RUBY - - expect_correction(<<~RUBY) - 3.times { create :user, :trait, key: val } - RUBY - end - - it 'flags usage of FactoryGirl.create_list' do - expect_offense(<<~RUBY) - FactoryGirl.create_list :user, 3 - ^^^^^^^^^^^ Prefer 3.times. - RUBY - - expect_correction(<<~RUBY) - 3.times { FactoryGirl.create :user } - RUBY - end - - it 'flags usage of FactoryGirl.create_list with a block' do - expect_offense(<<~RUBY) - FactoryGirl.create_list(:user, 3) { |user| user.points = rand(1000) } - ^^^^^^^^^^^ Prefer 3.times. - RUBY - - expect_correction(<<~RUBY) - 3.times { FactoryGirl.create(:user) { |user| user.points = rand(1000) } } - RUBY - end - - it 'ignores create method of other object' do - expect_no_offenses(<<~RUBY) - SomeFactory.create_list :user, 3 - RUBY - end - - context 'when Ruby 2.7', :ruby27 do - it 'ignores n.times with numblock' do - expect_no_offenses(<<~RUBY) - 3.times { create :user, position: _1 } - RUBY - end - end - end -end diff --git a/spec/rubocop/cop/rspec/factory_bot/factory_class_name_spec.rb b/spec/rubocop/cop/rspec/factory_bot/factory_class_name_spec.rb deleted file mode 100644 index b2d1705e6..000000000 --- a/spec/rubocop/cop/rspec/factory_bot/factory_class_name_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Cop::RSpec::FactoryBot::FactoryClassName do - def inspected_source_filename - 'spec/factories.rb' - end - - context 'when passing block' do - it 'flags passing a class' do - expect_offense(<<~RUBY) - factory :foo, class: Foo do - ^^^ Pass 'Foo' string instead of `Foo` constant. - end - RUBY - - expect_correction(<<~RUBY) - factory :foo, class: 'Foo' do - end - RUBY - end - - it 'flags passing a class from global namespace' do - expect_offense(<<~RUBY) - factory :foo, class: ::Foo do - ^^^^^ Pass 'Foo' string instead of `Foo` constant. - end - RUBY - - expect_correction(<<~RUBY) - factory :foo, class: '::Foo' do - end - RUBY - end - - it 'flags passing a subclass' do - expect_offense(<<~RUBY) - factory :foo, class: Foo::Bar do - ^^^^^^^^ Pass 'Foo::Bar' string instead of `Foo::Bar` constant. - end - RUBY - - expect_correction(<<~RUBY) - factory :foo, class: 'Foo::Bar' do - end - RUBY - end - - it 'ignores passing class name' do - expect_no_offenses(<<~RUBY) - factory :foo, class: 'Foo' do - end - RUBY - end - - it 'ignores passing Hash' do - expect_no_offenses(<<~RUBY) - factory :foo, class: Hash do - end - RUBY - end - - it 'ignores passing OpenStruct' do - expect_no_offenses(<<~RUBY) - factory :foo, class: OpenStruct do - end - RUBY - end - end - - context 'when not passing block' do - it 'flags passing a class' do - expect_offense(<<~RUBY) - factory :foo, class: Foo - ^^^ Pass 'Foo' string instead of `Foo` constant. - RUBY - - expect_correction(<<~RUBY) - factory :foo, class: 'Foo' - RUBY - end - - it 'ignores passing class name' do - expect_no_offenses(<<~RUBY) - factory :foo, class: 'Foo' - RUBY - end - end -end diff --git a/spec/rubocop/cop/rspec/factory_bot/factory_name_style_spec.rb b/spec/rubocop/cop/rspec/factory_bot/factory_name_style_spec.rb deleted file mode 100644 index 40c62002c..000000000 --- a/spec/rubocop/cop/rspec/factory_bot/factory_name_style_spec.rb +++ /dev/null @@ -1,229 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Cop::RSpec::FactoryBot::FactoryNameStyle, :config do - let(:cop_config) do - { 'EnforcedStyle' => enforced_style } - end - - context 'when EnforcedStyle is :symbol' do - let(:enforced_style) { :symbol } - - it 'registers an offense when using `create` with string name' do - expect_offense(<<~RUBY) - create('user') - ^^^^^^ Use symbol to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - create(:user) - RUBY - end - - it 'registers an offense when using `create` with string name and ' \ - 'multiline method calls' do - expect_offense(<<~RUBY) - create('user', - ^^^^^^ Use symbol to refer to a factory. - username: "PETER", - peter: "USERNAME") - RUBY - - expect_correction(<<~RUBY) - create(:user, - username: "PETER", - peter: "USERNAME") - RUBY - end - - it 'registers an offense when using `build` with string name' do - expect_offense(<<~RUBY) - build 'user' - ^^^^^^ Use symbol to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - build :user - RUBY - end - - it 'registers an offense when using `create` with an explicit receiver' do - expect_offense(<<~RUBY) - FactoryBot.create('user') - ^^^^^^ Use symbol to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - FactoryBot.create(:user) - RUBY - end - - it 'does not register an offense when using `create` with symbol name`' do - expect_no_offenses(<<~RUBY) - create(:user) - RUBY - end - - it 'does not register an offense when using `build` with symbol name`' do - expect_no_offenses(<<~RUBY) - build(:user) - RUBY - end - - it 'does not register an offense when using `create` ' \ - 'with string interpolation name`' do - expect_no_offenses(<<~RUBY) - create("user_\#{type}") - RUBY - end - - it 'does not register an offense when using `build` ' \ - 'with string interpolation name`' do - expect_no_offenses(<<~RUBY) - build("user_\#{'a'}") - RUBY - end - - it 'does not register an offense when using `create` ' \ - 'with keyword argument' do - expect_no_offenses(<<~RUBY) - create user: :foo - RUBY - end - - it 'does not register an offense when using `build` ' \ - 'with keyword argument' do - expect_no_offenses(<<~RUBY) - build user: :foo - RUBY - end - end - - context 'when EnforcedStyle is :string' do - let(:enforced_style) { :string } - - it 'registers an offense when using `create` with symbol name' do - expect_offense(<<~RUBY) - create(:user) - ^^^^^ Use string to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - create("user") - RUBY - end - - it 'registers an offense when using `create` with symbol name and ' \ - 'multiline method calls' do - expect_offense(<<~RUBY) - create(:user, - ^^^^^ Use string to refer to a factory. - username: "PETER", - peter: "USERNAME") - RUBY - - expect_correction(<<~RUBY) - create("user", - username: "PETER", - peter: "USERNAME") - RUBY - end - - it 'registers an offense when using `build` with symbol name' do - expect_offense(<<~RUBY) - build :user - ^^^^^ Use string to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - build "user" - RUBY - end - - it 'registers an offense when using `create` with an explicit receiver' do - expect_offense(<<~RUBY) - FactoryBot.create(:user) - ^^^^^ Use string to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - FactoryBot.create("user") - RUBY - end - - it 'registers an offense when using `create` with a method call' do - expect_offense(<<~RUBY) - do_something create(:user) - ^^^^^ Use string to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - do_something create("user") - RUBY - end - - it 'registers an offense when using `build` with a method call' do - expect_offense(<<~RUBY) - do_something build(:user) - ^^^^^ Use string to refer to a factory. - RUBY - - expect_correction(<<~RUBY) - do_something build("user") - RUBY - end - - it 'does not register an offense when using `create` with string name`' do - expect_no_offenses(<<~RUBY) - create('user') - RUBY - end - - it 'does not register an offense when using `build` with string name`' do - expect_no_offenses(<<~RUBY) - build('user') - RUBY - end - - it 'does not register an offense when using `create` ' \ - 'with a local variable' do - expect_no_offenses(<<~RUBY) - create(user) - RUBY - end - - it 'does not register an offense when using `build` ' \ - 'with a local variable' do - expect_no_offenses(<<~RUBY) - build(user) - RUBY - end - - it 'does not register an offense when using `create` ' \ - 'with string interpolation name`' do - expect_no_offenses(<<~RUBY) - create("user_\#{type}") - RUBY - end - - it 'does not register an offense when using `build` ' \ - 'with string interpolation name`' do - expect_no_offenses(<<~RUBY) - build("user_\#{'a'}") - RUBY - end - - it 'does not register an offense when using `create` ' \ - 'with keyword argument' do - expect_no_offenses(<<~RUBY) - create user: :foo - RUBY - end - - it 'does not register an offense when using `build` ' \ - 'with keyword argument' do - expect_no_offenses(<<~RUBY) - build user: :foo - RUBY - end - end -end diff --git a/spec/rubocop/cop/rspec/factory_bot/syntax_methods_spec.rb b/spec/rubocop/cop/rspec/factory_bot/syntax_methods_spec.rb deleted file mode 100644 index 902849592..000000000 --- a/spec/rubocop/cop/rspec/factory_bot/syntax_methods_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RuboCop::Cop::RSpec::FactoryBot::SyntaxMethods, :config do - described_class::RESTRICT_ON_SEND.each do |method| - it 'does not register an offense when used outside an example group' do - expect_no_offenses(<<~RUBY) - FactoryBot.#{method}(:bar) - RUBY - end - - it "does not register an offense for `#{method}`" do - expect_no_offenses(<<~RUBY) - RSpec.describe Foo do - let(:bar) { #{method}(:bar) } - end - RUBY - end - - it "registers an offense for `FactoryBot.#{method}`" do - expect_offense(<<~RUBY, method: method) - describe Foo do - let(:bar) { FactoryBot.%{method}(:bar) } - ^^^^^^^^^^^^{method} Use `%{method}` from `FactoryBot::Syntax::Methods`. - end - RUBY - - expect_correction(<<~RUBY) - describe Foo do - let(:bar) { #{method}(:bar) } - end - RUBY - end - - it "registers an offense for `FactoryBot.#{method}` in a shared group" do - expect_offense(<<~RUBY, method: method) - shared_examples_for Foo do - let(:bar) { FactoryBot.%{method}(:bar) } - ^^^^^^^^^^^^{method} Use `%{method}` from `FactoryBot::Syntax::Methods`. - end - RUBY - - expect_correction(<<~RUBY) - shared_examples_for Foo do - let(:bar) { #{method}(:bar) } - end - RUBY - end - - it "registers an offense for `::FactoryBot.#{method}`" do - expect_offense(<<~RUBY, method: method) - RSpec.describe Foo do - let(:bar) { ::FactoryBot.%{method}(:bar) } - ^^^^^^^^^^^^^^{method} Use `%{method}` from `FactoryBot::Syntax::Methods`. - end - RUBY - - expect_correction(<<~RUBY) - RSpec.describe Foo do - let(:bar) { #{method}(:bar) } - end - RUBY - end - end -end diff --git a/tasks/cops_documentation.rake b/tasks/cops_documentation.rake index 3cf06e777..2010920ac 100644 --- a/tasks/cops_documentation.rake +++ b/tasks/cops_documentation.rake @@ -41,6 +41,30 @@ task generate_cops_documentation: :yard_for_generate_documentation do end global.enlist(cop) end + %w[ + RuboCop::Cop::RSpec::FactoryBot::AttributeDefinedStatically + RuboCop::Cop::RSpec::FactoryBot::ConsistentParenthesesStyle + RuboCop::Cop::RSpec::FactoryBot::CreateList + RuboCop::Cop::RSpec::FactoryBot::FactoryClassName + RuboCop::Cop::RSpec::FactoryBot::FactoryNameStyle + RuboCop::Cop::RSpec::FactoryBot::SyntaxMethods + ].each do |extracted_cop| + cop = Class.const_get(extracted_cop) + class << cop + def badge + RuboCop::Cop::Badge.for(name) + end + + def name + super.sub('::FactoryBot::', '::RSpec::FactoryBot::') + end + + def department + :'RSpec/FactoryBot' + end + end + global.enlist(cop) + end generator = CopsDocumentationGenerator.new( departments: %w[RSpec/Capybara RSpec/FactoryBot RSpec/Rails RSpec] From a37fc2cce40dbcc90d3c078888d559b7daccd7be Mon Sep 17 00:00:00 2001 From: ydah <13041216+ydah@users.noreply.github.com> Date: Sat, 6 May 2023 10:52:11 +0900 Subject: [PATCH 2/2] Release v2.22.0 --- CHANGELOG.md | 2 ++ docs/antora.yml | 2 +- lib/rubocop/rspec/version.rb | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c58ca07d..5493f0cf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +## 2.22.0 (2023-05-06) + - Extract factory_bot cops to a separate repository, [`rubocop-factory_bot`](https://github.com/rubocop/rubocop-factory_bot). The `rubocop-factory_bot` repository is a dependency of `rubocop-rspec` and the factory_bot cops are aliased (`RSpec/FactoryBot/Foo` == `FactoryBot/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@ydah]) ## 2.21.0 (2023-05-05) diff --git a/docs/antora.yml b/docs/antora.yml index 51a533a15..6e10ebe3d 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,5 +1,5 @@ name: rubocop-rspec title: RuboCop RSpec -version: ~ +version: '2.22' nav: - modules/ROOT/nav.adoc diff --git a/lib/rubocop/rspec/version.rb b/lib/rubocop/rspec/version.rb index a8216694e..746e8808c 100644 --- a/lib/rubocop/rspec/version.rb +++ b/lib/rubocop/rspec/version.rb @@ -4,7 +4,7 @@ module RuboCop module RSpec # Version information for the RSpec RuboCop plugin. module Version - STRING = '2.21.0' + STRING = '2.22.0' end end end