diff --git a/Gemfile b/Gemfile index 10f2976a..e58ca710 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ gemspec gem 'bump' gem 'danger' +gem 'irb' gem 'rack' gem 'rake' gem 'rspec', '~> 3.11' diff --git a/config/default.yml b/config/default.yml index 24d5ebb4..71f46ad0 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1,4 +1,14 @@ --- +AllCops: + # What version of FactoryBot is the inspected code using? If a value is specified + # for `TargetFactoryBotVersion` then it is used. Acceptable values are specified + # as a float (e.g., 6.1); the patch version of FactoryBot should not be included. + # If `TargetFactoryBotVersion` is not set, RuboCop will parse the Gemfile.lock or + # gems.locked file to find the version of FactoryBot that has been bound to the + # application. If neither of those files exist, RuboCop will use FactoryBot 6.1 + # as the default. + TargetFactoryBotVersion: ~ + FactoryBot: Enabled: true Include: @@ -119,6 +129,12 @@ FactoryBot/IdSequence: VersionAdded: '2.24' Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/IdSequence +FactoryBot/RedundantEnumTrait: + Description: Checks for redundant enum traits in FactoryBot definitions. + Enabled: pending + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/RedundantEnumTrait + FactoryBot/RedundantFactoryOption: Description: Checks for redundant `factory` option. Enabled: pending diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index b5f503f5..b3625038 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -11,6 +11,7 @@ * xref:cops_factorybot.adoc#factorybotfactoryclassname[FactoryBot/FactoryClassName] * xref:cops_factorybot.adoc#factorybotfactorynamestyle[FactoryBot/FactoryNameStyle] * xref:cops_factorybot.adoc#factorybotidsequence[FactoryBot/IdSequence] +* xref:cops_factorybot.adoc#factorybotredundantenumtrait[FactoryBot/RedundantEnumTrait] * xref:cops_factorybot.adoc#factorybotredundantfactoryoption[FactoryBot/RedundantFactoryOption] * xref:cops_factorybot.adoc#factorybotsyntaxmethods[FactoryBot/SyntaxMethods] diff --git a/docs/modules/ROOT/pages/cops_factorybot.adoc b/docs/modules/ROOT/pages/cops_factorybot.adoc index 011672ae..a9a7eec2 100644 --- a/docs/modules/ROOT/pages/cops_factorybot.adoc +++ b/docs/modules/ROOT/pages/cops_factorybot.adoc @@ -682,6 +682,56 @@ end * https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/IdSequence +[#factorybotredundantenumtrait] +== FactoryBot/RedundantEnumTrait + +NOTE: Required FactoryBot version: 6.1 + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Always +| <> +| - +|=== + +Checks for redundant enum traits in FactoryBot definitions. + +[#examples-factorybotredundantenumtrait] +=== Examples + +[source,ruby] +---- +# bad +factory :task do + trait :queued do + status { Task.statuses[:queued] } + end +end + +# good +factory :task do +end +---- + +[#configurable-attributes-factorybotredundantenumtrait] +=== Configurable attributes + +|=== +| Name | Default value | Configurable values + +| TargetFactoryBotVersion +| `6.1` +| Float +|=== + +[#references-factorybotredundantenumtrait] +=== References + +* https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/RedundantEnumTrait + [#factorybotredundantfactoryoption] == FactoryBot/RedundantFactoryOption diff --git a/lib/rubocop-factory_bot.rb b/lib/rubocop-factory_bot.rb index ea5648c8..4aeb3651 100644 --- a/lib/rubocop-factory_bot.rb +++ b/lib/rubocop-factory_bot.rb @@ -4,6 +4,7 @@ require 'yaml' require 'rubocop' +require_relative 'rubocop/config' require_relative 'rubocop/factory_bot/factory_bot' require_relative 'rubocop/factory_bot/language' @@ -11,5 +12,6 @@ require_relative 'rubocop/factory_bot/version' require_relative 'rubocop/cop/factory_bot/mixin/configurable_explicit_only' +require_relative 'rubocop/cop/factory_bot/mixin/target_factory_bot_version' require_relative 'rubocop/cop/factory_bot_cops' diff --git a/lib/rubocop/config.rb b/lib/rubocop/config.rb new file mode 100644 index 00000000..0d286376 --- /dev/null +++ b/lib/rubocop/config.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module RuboCop + # Extension of RuboCop's Config class. + class Config + DEFAULT_FACTORY_BOT_VERSION = 6.0 + + def target_factory_bot_version + @target_factory_bot_version ||= + if for_all_cops['TargetFactoryBotVersion'] + for_all_cops['TargetFactoryBotVersion'].to_f + elsif target_factory_bot_version_from_bundler_lock_file + target_factory_bot_version_from_bundler_lock_file + else + DEFAULT_FACTORY_BOT_VERSION + end + end + + # @return [Float, nil] The FactoryBot version as a `major.minor` Float. + def target_factory_bot_version_from_bundler_lock_file + @target_factory_bot_version_from_bundler_lock_file ||= + read_factory_bot_version_from_bundler_lock_file + end + + # @return [Float, nil] The FactoryBot version as a `major.minor` Float. + def read_factory_bot_version_from_bundler_lock_file + return unless gem_versions_in_target + + factory_bot_in_target = gem_versions_in_target['factory_bot'] + return unless factory_bot_in_target + + gem_version_to_major_minor_float(factory_bot_in_target) + end + end +end diff --git a/lib/rubocop/cop/factory_bot/association_style.rb b/lib/rubocop/cop/factory_bot/association_style.rb index 7f823cb6..5f6a02d8 100644 --- a/lib/rubocop/cop/factory_bot/association_style.rb +++ b/lib/rubocop/cop/factory_bot/association_style.rb @@ -60,7 +60,7 @@ module FactoryBot # factory :user do # email # end - class AssociationStyle < ::RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength + class AssociationStyle < Base # rubocop:disable Metrics/ClassLength extend AutoCorrector include ConfigurableEnforcedStyle diff --git a/lib/rubocop/cop/factory_bot/attribute_defined_statically.rb b/lib/rubocop/cop/factory_bot/attribute_defined_statically.rb index 33018417..7e79ce96 100644 --- a/lib/rubocop/cop/factory_bot/attribute_defined_statically.rb +++ b/lib/rubocop/cop/factory_bot/attribute_defined_statically.rb @@ -24,7 +24,7 @@ module FactoryBot # # good # count { 1 } # - class AttributeDefinedStatically < ::RuboCop::Cop::Base + class AttributeDefinedStatically < Base extend AutoCorrector MSG = 'Use a block to declare attribute values.' diff --git a/lib/rubocop/cop/factory_bot/base.rb b/lib/rubocop/cop/factory_bot/base.rb new file mode 100644 index 00000000..2c353dc0 --- /dev/null +++ b/lib/rubocop/cop/factory_bot/base.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module FactoryBot + # Base class for FactoryBot cops. + class Base < ::RuboCop::Cop::Base + def target_factory_bot_version + @config.target_factory_bot_version + end + end + end + end +end diff --git a/lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb b/lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb index d9b28b58..77b72fc4 100644 --- a/lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb +++ b/lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb @@ -57,7 +57,7 @@ module FactoryBot # create :user # build :user # - class ConsistentParenthesesStyle < ::RuboCop::Cop::Base + class ConsistentParenthesesStyle < Base extend AutoCorrector include ConfigurableEnforcedStyle include ConfigurableExplicitOnly diff --git a/lib/rubocop/cop/factory_bot/create_list.rb b/lib/rubocop/cop/factory_bot/create_list.rb index 1fc95aef..7354f10a 100644 --- a/lib/rubocop/cop/factory_bot/create_list.rb +++ b/lib/rubocop/cop/factory_bot/create_list.rb @@ -58,7 +58,7 @@ module FactoryBot # create_list :user, 3 # 3.times { create :user } # - class CreateList < ::RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength + class CreateList < Base # rubocop:disable Metrics/ClassLength extend AutoCorrector include ConfigurableEnforcedStyle include RuboCop::FactoryBot::Language diff --git a/lib/rubocop/cop/factory_bot/excessive_create_list.rb b/lib/rubocop/cop/factory_bot/excessive_create_list.rb index 2d3f51af..eee182ec 100644 --- a/lib/rubocop/cop/factory_bot/excessive_create_list.rb +++ b/lib/rubocop/cop/factory_bot/excessive_create_list.rb @@ -23,7 +23,7 @@ module FactoryBot # # good # create_list(:merge_request, 15, state: :opened) # - class ExcessiveCreateList < ::RuboCop::Cop::Base + class ExcessiveCreateList < Base include ConfigurableExplicitOnly MESSAGE = diff --git a/lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb b/lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb index 4d9f6b39..0477e0c4 100644 --- a/lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb +++ b/lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb @@ -26,7 +26,7 @@ module FactoryBot # profile { association :profile } # end # - class FactoryAssociationWithStrategy < ::RuboCop::Cop::Base + class FactoryAssociationWithStrategy < Base MSG = 'Use an implicit, explicit or inline definition instead of ' \ 'hard coding a strategy for setting association within factory.' diff --git a/lib/rubocop/cop/factory_bot/factory_class_name.rb b/lib/rubocop/cop/factory_bot/factory_class_name.rb index 26d366e8..a0bb2bb0 100644 --- a/lib/rubocop/cop/factory_bot/factory_class_name.rb +++ b/lib/rubocop/cop/factory_bot/factory_class_name.rb @@ -19,7 +19,7 @@ module FactoryBot # factory :foo, class: 'Foo' do # end # - class FactoryClassName < ::RuboCop::Cop::Base + class FactoryClassName < Base extend AutoCorrector MSG = "Pass '%s' string instead of `%s` " \ diff --git a/lib/rubocop/cop/factory_bot/factory_name_style.rb b/lib/rubocop/cop/factory_bot/factory_name_style.rb index 2184c80d..e5cb9858 100644 --- a/lib/rubocop/cop/factory_bot/factory_name_style.rb +++ b/lib/rubocop/cop/factory_bot/factory_name_style.rb @@ -48,7 +48,7 @@ module FactoryBot # FactoryBot.create(:user) # create(:user) # - class FactoryNameStyle < ::RuboCop::Cop::Base + class FactoryNameStyle < Base extend AutoCorrector include ConfigurableEnforcedStyle include RuboCop::FactoryBot::Language diff --git a/lib/rubocop/cop/factory_bot/id_sequence.rb b/lib/rubocop/cop/factory_bot/id_sequence.rb index 055eee2d..65483c4e 100644 --- a/lib/rubocop/cop/factory_bot/id_sequence.rb +++ b/lib/rubocop/cop/factory_bot/id_sequence.rb @@ -16,7 +16,7 @@ module FactoryBot # sequence :some_non_id_column # end # - class IdSequence < ::RuboCop::Cop::Base + class IdSequence < Base extend AutoCorrector include RangeHelp include RuboCop::FactoryBot::Language diff --git a/lib/rubocop/cop/factory_bot/mixin/target_factory_bot_version.rb b/lib/rubocop/cop/factory_bot/mixin/target_factory_bot_version.rb new file mode 100644 index 00000000..19d938ce --- /dev/null +++ b/lib/rubocop/cop/factory_bot/mixin/target_factory_bot_version.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module FactoryBot + # Common functionality for checking target factory_bot version. + module TargetFactoryBotVersion + # Informs the base RuboCop gem that it the FactoryBot version is checked + # via `requires_gem` API, without needing to call this + # `#support_target_factory_bot_version` method. + USES_REQUIRES_GEM_API = true + TARGET_GEM_NAME = 'factory_bot' # :nodoc: + + def minimum_target_factory_bot_version(version) + if respond_to?(:requires_gem) + case version + when Integer, Float then requires_gem(TARGET_GEM_NAME, + ">= #{version}") + when String then requires_gem(TARGET_GEM_NAME, version) + end + else + # Fallback path for previous versions of RuboCop which don't support + # the `requires_gem` API yet. + @minimum_target_factory_bot_version = version + end + end + + def support_target_factory_bot_version?(version) + pp version + if respond_to?(:requires_gem) + return false unless gem_requirements + + gem_requirement = gem_requirements[TARGET_GEM_NAME] + # If we have no requirement, then we support all versions + return true unless gem_requirement + + pp gem_requirement + + gem_requirement.satisfied_by?(Gem::Version.new(version)) + else + # Fallback path for previous versions of RuboCop which don't support + # the `requires_gem` API yet. + @minimum_target_factory_bot_version <= version + end + end + end + end + end +end diff --git a/lib/rubocop/cop/factory_bot/redundant_enum_trait.rb b/lib/rubocop/cop/factory_bot/redundant_enum_trait.rb new file mode 100644 index 00000000..5ef4c802 --- /dev/null +++ b/lib/rubocop/cop/factory_bot/redundant_enum_trait.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module FactoryBot + # Checks for redundant enum traits in FactoryBot definitions. + # + # @example + # # bad + # factory :task do + # trait :queued do + # status { Task.statuses[:queued] } + # end + # end + # + # # good + # factory :task do + # end + # + class RedundantEnumTrait < Base + extend TargetFactoryBotVersion + extend AutoCorrector + + MSG = 'This trait is redundant because enum traits are ' \ + 'automatically defined in FactoryBot 6.1 and later.' + RESTRICT_ON_SEND = %i[trait].freeze + minimum_target_factory_bot_version 6.1 + + # @!method redundant_enum_trait_body?(node) + def_node_matcher :redundant_enum_trait_body?, <<~PATTERN + (block + (send nil? $_attribute_name) + (args) + (send + (send (const ...) $_enum_plural_name) + :[] + (sym $_enum_key_name))) + PATTERN + + def on_send(node) + add_offense(node) + trait_block = node.last_argument + return unless trait_block&.block_type? + + attribute_definition = trait_block.body + return unless attribute_definition&.block_type? + + redundant_enum_trait_body?(attribute_definition) do |attr, enums, key| + next unless redundant?(node.first_argument.value, attr, enums, key) + + add_offense(node) do |corrector| + corrector.remove(node) + end + end + end + + private + + def redundant?(trait_name, attribute_name, enum_plural_name, + enum_key_name) + return false if trait_name != enum_key_name + + singular_enum = enum_plural_name.to_s.sub(/es$/, '').sub(/s$/, '') + singular_enum.to_sym == attribute_name + end + end + end + end +end diff --git a/lib/rubocop/cop/factory_bot/redundant_factory_option.rb b/lib/rubocop/cop/factory_bot/redundant_factory_option.rb index 5230e9dc..74f312c8 100644 --- a/lib/rubocop/cop/factory_bot/redundant_factory_option.rb +++ b/lib/rubocop/cop/factory_bot/redundant_factory_option.rb @@ -11,7 +11,7 @@ module FactoryBot # # # good # association :user - class RedundantFactoryOption < ::RuboCop::Cop::Base + class RedundantFactoryOption < Base extend AutoCorrector include RangeHelp diff --git a/lib/rubocop/cop/factory_bot/syntax_methods.rb b/lib/rubocop/cop/factory_bot/syntax_methods.rb index 683f239b..5b5fbb19 100644 --- a/lib/rubocop/cop/factory_bot/syntax_methods.rb +++ b/lib/rubocop/cop/factory_bot/syntax_methods.rb @@ -45,7 +45,7 @@ module FactoryBot # build(:bar) # attributes_for(:bar) # - class SyntaxMethods < ::RuboCop::Cop::Base + class SyntaxMethods < Base extend AutoCorrector include RangeHelp include RuboCop::FactoryBot::Language diff --git a/lib/rubocop/cop/factory_bot_cops.rb b/lib/rubocop/cop/factory_bot_cops.rb index ee3282d4..49258967 100644 --- a/lib/rubocop/cop/factory_bot_cops.rb +++ b/lib/rubocop/cop/factory_bot_cops.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative 'factory_bot/base' + require_relative 'factory_bot/association_style' require_relative 'factory_bot/attribute_defined_statically' require_relative 'factory_bot/consistent_parentheses_style' @@ -9,5 +11,6 @@ require_relative 'factory_bot/factory_class_name' require_relative 'factory_bot/factory_name_style' require_relative 'factory_bot/id_sequence' +require_relative 'factory_bot/redundant_enum_trait' require_relative 'factory_bot/redundant_factory_option' require_relative 'factory_bot/syntax_methods' diff --git a/spec/project/default_config_spec.rb b/spec/project/default_config_spec.rb index e774314e..2a575690 100644 --- a/spec/project/default_config_spec.rb +++ b/spec/project/default_config_spec.rb @@ -13,12 +13,13 @@ let(:cop_names) do glob = SpecHelper::ROOT.join('lib', 'rubocop', 'cop', 'factory_bot', '*.rb') - Pathname.glob(glob).map do |file| + names = Pathname.glob(glob).map do |file| file_name = file.basename('.rb').to_s cop_name = file_name.gsub(/(^|_)(.)/) { Regexp.last_match(2).upcase } namespace = namespaces[file.dirname.basename.to_s] "#{namespace}/#{cop_name}" end + names.reject { |name| name == 'FactoryBot/Base' } end let(:config_keys) do @@ -53,7 +54,7 @@ def cop_configuration(config_key) it 'has configuration for all cops' do expect(default_config.keys) - .to match_array(config_keys) + .to match_array(['AllCops'] + config_keys) end it 'sorts configuration keys alphabetically with nested namespaces last' do diff --git a/spec/rubocop/config_spec.rb b/spec/rubocop/config_spec.rb new file mode 100644 index 00000000..c539c708 --- /dev/null +++ b/spec/rubocop/config_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Config do + include FileHelper + + subject(:configuration) { described_class.new(hash, loaded_path) } + + let(:loaded_path) { 'example/.rubocop.yml' } + + describe '#target_factory_bot_version' do + context 'when TargetFactoryBotVersion is set' do + let(:hash) do + { + 'AllCops' => { + 'TargetFactoryBotVersion' => factory_bot_version + } + } + end + + context 'with patch version' do + let(:factory_bot_version) { '6.2.1' } + let(:factory_bot_version_to_f) { 6.2 } + + it 'truncates the patch part and converts to a float' do + expect(configuration.target_factory_bot_version).to eq factory_bot_version_to_f + end + end + + context 'correctly' do + let(:factory_bot_version) { 6.2 } + + it 'uses TargetFactoryBotVersion' do + expect(configuration.target_factory_bot_version).to eq factory_bot_version + end + end + end + + context 'when TargetFactoryBotVersion is not set', :isolated_environment do + let(:hash) do + { + 'AllCops' => {} + } + end + + context 'and lock files do not exist' do + it 'uses the default factory_bot version' do + default = described_class::DEFAULT_FACTORY_BOT_VERSION + expect(configuration.target_factory_bot_version).to eq default + end + end + + ['Gemfile.lock', 'gems.locked'].each do |file_name| + context "and #{file_name} exists" do + let(:base_path) { configuration.base_dir_for_path_parameters } + let(:lock_file_path) { File.join(base_path, file_name) } + + it "uses the single digit FactoryBot version in #{file_name}" do + content = + <<~LOCKFILE + GEM + remote: https://rubygems.org/ + specs: + ffaker (2.20.0) + factory_bot (6.2.0) + activesupport (>= 4.2.0) + + PLATFORMS + ruby + + DEPENDENCIES + factory_bot (~> 6.2) + + BUNDLED WITH + 2.3.14 + LOCKFILE + create_file(lock_file_path, content) + expect(configuration.target_factory_bot_version).to eq 6.2 + end + + it "uses the multi digit FactoryBot version in #{file_name}" do + content = + <<~LOCKFILE + GEM + remote: https://rubygems.org/ + specs: + factory_bot (100.22.33) + activesupport (>= 4.2.0) + + PLATFORMS + ruby + + DEPENDENCIES + factory_bot + + BUNDLED WITH + 2.3.14 + LOCKFILE + create_file(lock_file_path, content) + expect(configuration.target_factory_bot_version).to eq 100.22 + end + + it "does not use the DEPENDENCIES FactoryBot version in #{file_name}" do + content = + <<~LOCKFILE + GEM + remote: https://rubygems.org/ + specs: + activesupport (7.0.3) + + PLATFORMS + ruby + + DEPENDENCIES + factory_bot (= 99.88.77) + + BUNDLED WITH + 2.3.14 + LOCKFILE + create_file(lock_file_path, content) + expect(configuration.target_factory_bot_version).not_to eq 99.88 + end + + it "uses the default FactoryBot when FactoryBot is not in #{file_name}" do + content = + <<~LOCKFILE + GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.2) + ast (2.4.0) + + PLATFORMS + ruby + + DEPENDENCIES + bundler (~> 2.3) + + BUNDLED WITH + 2.3.14 + LOCKFILE + create_file(lock_file_path, content) + default = described_class::DEFAULT_FACTORY_BOT_VERSION + expect(configuration.target_factory_bot_version).to eq default + end + end + end + end + end +end diff --git a/spec/rubocop/cop/factory_bot/redundant_enum_trait_spec.rb b/spec/rubocop/cop/factory_bot/redundant_enum_trait_spec.rb new file mode 100644 index 00000000..7c7507a3 --- /dev/null +++ b/spec/rubocop/cop/factory_bot/redundant_enum_trait_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::FactoryBot::RedundantEnumTrait, :config do + context 'when FactoryBot 6.1', :factory_bot61 do + it 'registers an offense and corrects a redundant enum trait' do + expect_offense(<<~RUBY) + FactoryBot.define do + factory :task do + trait :queued do + ^^^^^^^^^^^^^^^ This trait is redundant because enum traits are automatically defined in FactoryBot 6.1 and later. + status { Task.statuses[:queued] } + end + end + end + RUBY + + expect_correction(<<~RUBY) + FactoryBot.define do + factory :task do + end + end + RUBY + end + + it 'registers an offense for different attribute and model names' do + expect_offense(<<~RUBY) + factory :job do + trait :failed do + ^^^^^^^^^^^^^^ This trait is redundant because enum traits are automatically defined in FactoryBot 6.1 and later. + state { Job.states[:failed] } + end + end + RUBY + end + + it 'does not register an offense when trait name and enum key mismatch' do + expect_no_offenses(<<~RUBY) + factory :task do + trait :in_progress do + status { Task.statuses[:started] } + end + end + RUBY + end + + it 'does not register an offense for traits with different structures' do + expect_no_offenses(<<~RUBY) + factory :task do + trait :queued do + status { :queued } + end + end + RUBY + end + + it 'does not register an offense for traits with multiple attributes' do + expect_no_offenses(<<~RUBY) + factory :task do + trait :queued do + name { 'A queued task' } + status { Task.statuses[:queued] } + end + end + RUBY + end + end + + context 'when FactoryBot 6.0', :factory_bot60 do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + FactoryBot.define do + factory :task do + trait :queued do + status { Task.statuses[:queued] } + end + end + end + RUBY + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 84b84d74..7922a75e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -47,3 +47,4 @@ module SpecHelper $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'rubocop-factory_bot' +require_relative 'support/shared_contexts' diff --git a/spec/support/file_helper.rb b/spec/support/file_helper.rb new file mode 100644 index 00000000..23fc978c --- /dev/null +++ b/spec/support/file_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'fileutils' + +module FileHelper + def create_file(file_path, content) + file_path = File.expand_path(file_path) + dir_path = File.dirname(file_path) + FileUtils.mkdir_p dir_path + File.open(file_path, 'w') do |file| + if content.is_a?(String) + file.puts content + elsif content.is_a?(Array) + file.puts content.join("\n") + end + end + end + + # rubocop:disable InternalAffairs/CreateEmptyFile + def create_empty_file(file_path) + create_file(file_path, '') + end + # rubocop:enable InternalAffairs/CreateEmptyFile +end diff --git a/spec/support/shared_contexts.rb b/spec/support/shared_contexts.rb new file mode 100644 index 00000000..5b0687e8 --- /dev/null +++ b/spec/support/shared_contexts.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.shared_context 'with FactoryBot 6.0', :factory_bot60 do + let(:all_cops_config) do + super().merge('TargetFactoryBotVersion' => 6.0) + end +end + +RSpec.shared_context 'with FactoryBot 6.1', :factory_bot61 do + let(:all_cops_config) do + super().merge('TargetFactoryBotVersion' => 6.1) + end +end diff --git a/tasks/cops_documentation.rake b/tasks/cops_documentation.rake index dbd56b51..099fe554 100644 --- a/tasks/cops_documentation.rake +++ b/tasks/cops_documentation.rake @@ -12,8 +12,18 @@ end desc 'Generate docs of all cops departments' task generate_cops_documentation: :yard_for_generate_documentation do + # NOTE: Insert minimum_target_factory_bot_version after ruby version + required_factory_bot_version = lambda do |data| + unless (version = data.cop.gem_requirements[RuboCop::Cop::FactoryBot::TargetFactoryBotVersion::TARGET_GEM_NAME]) + return '' + end + + "NOTE: Required FactoryBot version: #{version.requirements[0][1]}\n\n" + end + extra_info = { required_ruby_version: required_factory_bot_version } generator = CopsDocumentationGenerator.new( departments: %w[FactoryBot], + extra_info: extra_info, plugin_name: 'rubocop-factory_bot' ) generator.call