Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ gemspec

gem 'bump'
gem 'danger'
gem 'irb'
gem 'rack'
gem 'rake'
gem 'rspec', '~> 3.11'
Expand Down
16 changes: 16 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/RedundantEnumTrait

FactoryBot/RedundantFactoryOption:
Description: Checks for redundant `factory` option.
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
50 changes: 50 additions & 0 deletions docs/modules/ROOT/pages/cops_factorybot.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
| <<next>>
| -
|===

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

Expand Down
2 changes: 2 additions & 0 deletions lib/rubocop-factory_bot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
require 'yaml'

require 'rubocop'
require_relative 'rubocop/config'

require_relative 'rubocop/factory_bot/factory_bot'
require_relative 'rubocop/factory_bot/language'
require_relative 'rubocop/factory_bot/plugin'
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'
35 changes: 35 additions & 0 deletions lib/rubocop/config.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/association_style.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand Down
14 changes: 14 additions & 0 deletions lib/rubocop/cop/factory_bot/base.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module FactoryBot
# create :user
# build :user
#
class ConsistentParenthesesStyle < ::RuboCop::Cop::Base
class ConsistentParenthesesStyle < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
include ConfigurableExplicitOnly
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/create_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/excessive_create_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.'

Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/factory_class_name.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module FactoryBot
# factory :foo, class: 'Foo' do
# end
#
class FactoryClassName < ::RuboCop::Cop::Base
class FactoryClassName < Base
extend AutoCorrector

MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/factory_name_style.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/id_sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions lib/rubocop/cop/factory_bot/mixin/target_factory_bot_version.rb
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions lib/rubocop/cop/factory_bot/redundant_enum_trait.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/redundant_factory_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module FactoryBot
#
# # good
# association :user
class RedundantFactoryOption < ::RuboCop::Cop::Base
class RedundantFactoryOption < Base
extend AutoCorrector

include RangeHelp
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/factory_bot/syntax_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading