Skip to content

Commit 12616e1

Browse files
committed
Extract Factory Bot cops
1 parent 75c16dd commit 12616e1

22 files changed

+228
-2066
lines changed

.simplecov

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
SimpleCov.start do
44
enable_coverage :branch
5-
minimum_coverage line: 99.60, branch: 95.32
5+
minimum_coverage line: 99.60, branch: 94.84
66
add_filter '/spec/'
77
add_filter '/vendor/bundle/'
88
end

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Master (Unreleased)
44

5+
- 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])
6+
57
## 2.21.0 (2023-05-05)
68

79
- Fix a false positive in `RSpec/IndexedLet` with suffixes after index-like numbers. ([@pirj])

config/obsoletion.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ renamed:
2121
RSpec/Capybara/SpecificFinders: Capybara/SpecificFinders
2222
RSpec/Capybara/SpecificMatcher: Capybara/SpecificMatcher
2323
RSpec/Capybara/VisibilityMatcher: Capybara/VisibilityMatcher
24+
RSpec/FactoryBot/AttributeDefinedStatically: FactoryBot/AttributeDefinedStatically
25+
RSpec/FactoryBot/ConsistentParenthesesStyle: FactoryBot/ConsistentParenthesesStyle
26+
RSpec/FactoryBot/CreateList: FactoryBot/CreateList
27+
RSpec/FactoryBot/FactoryClassName: FactoryBot/FactoryClassName
28+
RSpec/FactoryBot/FactoryNameStyle: FactoryBot/FactoryNameStyle
29+
RSpec/FactoryBot/SyntaxMethods: FactoryBot/SyntaxMethods

docs/modules/ROOT/pages/cops_rspec_factorybot.adoc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,9 @@ create_list :user, 3
204204
Use string value when setting the class attribute explicitly.
205205

206206
This cop would promote faster tests by lazy-loading of
207-
application files. Also, this could help you suppress potential bugs
208-
in combination with external libraries by avoiding a preload of
209-
application files from the factory files.
207+
application files. Also, this could help you suppress potential
208+
bugs in combination with external libraries by avoiding a preload
209+
of application files from the factory files.
210210

211211
=== Examples
212212

@@ -312,8 +312,8 @@ cannot verify whether you already include
312312
`FactoryBot::Syntax::Methods` in your test suite.
313313

314314
If you're using Rails, add the following configuration to
315-
`spec/support/factory_bot.rb` and be sure to require that file in
316-
`rails_helper.rb`:
315+
`spec/support/factory_bot.rb` and be sure to require that file
316+
in `rails_helper.rb`:
317317

318318
[source,ruby]
319319
----

lib/rubocop-rspec.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
require 'rubocop'
77
require 'rubocop-capybara'
8+
require 'rubocop-factory_bot'
89

910
require_relative 'rubocop/rspec'
1011
require_relative 'rubocop/rspec/inject'
@@ -16,8 +17,6 @@
1617
# Dependent on `RuboCop::RSpec::Language::NodePattern`.
1718
require_relative 'rubocop/rspec/language'
1819

19-
require_relative 'rubocop/rspec/factory_bot/language'
20-
2120
require_relative 'rubocop/cop/rspec/mixin/final_end_location'
2221
require_relative 'rubocop/cop/rspec/mixin/inside_example_group'
2322
require_relative 'rubocop/cop/rspec/mixin/location_help'
@@ -37,7 +36,6 @@
3736
require_relative 'rubocop/rspec/corrector/move_node'
3837
require_relative 'rubocop/rspec/example'
3938
require_relative 'rubocop/rspec/example_group'
40-
require_relative 'rubocop/rspec/factory_bot'
4139
require_relative 'rubocop/rspec/hook'
4240

4341
RuboCop::RSpec::Inject.defaults!

lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb

Lines changed: 25 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -4,124 +4,31 @@ module RuboCop
44
module Cop
55
module RSpec
66
module FactoryBot
7-
# Always declare attribute values as blocks.
8-
#
9-
# @example
10-
# # bad
11-
# kind [:active, :rejected].sample
12-
#
13-
# # good
14-
# kind { [:active, :rejected].sample }
15-
#
16-
# # bad
17-
# closed_at 1.day.from_now
18-
#
19-
# # good
20-
# closed_at { 1.day.from_now }
21-
#
22-
# # bad
23-
# count 1
24-
#
25-
# # good
26-
# count { 1 }
27-
#
28-
class AttributeDefinedStatically < ::RuboCop::Cop::Base
29-
extend AutoCorrector
30-
31-
MSG = 'Use a block to declare attribute values.'
32-
33-
# @!method value_matcher(node)
34-
def_node_matcher :value_matcher, <<-PATTERN
35-
(send _ !#reserved_method? $...)
36-
PATTERN
37-
38-
# @!method factory_attributes(node)
39-
def_node_matcher :factory_attributes, <<-PATTERN
40-
(block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } )
41-
PATTERN
42-
43-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
44-
attributes = factory_attributes(node) || []
45-
attributes = [attributes] unless attributes.is_a?(Array) # rubocop:disable Style/ArrayCoercion, Lint/RedundantCopDisableDirective
46-
47-
attributes.each do |attribute|
48-
next unless offensive_receiver?(attribute.receiver, node)
49-
next if proc?(attribute) || association?(attribute.first_argument)
50-
51-
add_offense(attribute) do |corrector|
52-
autocorrect(corrector, attribute)
53-
end
54-
end
55-
end
56-
57-
private
58-
59-
def autocorrect(corrector, node)
60-
if node.parenthesized?
61-
autocorrect_replacing_parens(corrector, node)
62-
else
63-
autocorrect_without_parens(corrector, node)
64-
end
65-
end
66-
67-
def offensive_receiver?(receiver, node)
68-
receiver.nil? ||
69-
receiver.self_type? ||
70-
receiver_matches_first_block_argument?(receiver, node)
71-
end
72-
73-
def receiver_matches_first_block_argument?(receiver, node)
74-
first_block_argument = node.arguments.first
75-
76-
!first_block_argument.nil? &&
77-
receiver.lvar_type? &&
78-
receiver.node_parts == first_block_argument.node_parts
79-
end
80-
81-
def proc?(attribute)
82-
value_matcher(attribute).to_a.all?(&:block_pass_type?)
83-
end
84-
85-
# @!method association?(node)
86-
def_node_matcher :association?, '(hash <(pair (sym :factory) _) ...>)'
87-
88-
def autocorrect_replacing_parens(corrector, node)
89-
left_braces, right_braces = braces(node)
90-
91-
corrector.replace(node.location.begin, " #{left_braces}")
92-
corrector.replace(node.location.end, right_braces)
93-
end
94-
95-
def autocorrect_without_parens(corrector, node)
96-
left_braces, right_braces = braces(node)
97-
98-
argument = node.first_argument
99-
expression = argument.source_range
100-
corrector.insert_before(expression, left_braces)
101-
corrector.insert_after(expression, right_braces)
102-
end
103-
104-
def braces(node)
105-
if value_hash_without_braces?(node.first_argument)
106-
['{ { ', ' } }']
107-
else
108-
['{ ', ' }']
109-
end
110-
end
111-
112-
def value_hash_without_braces?(node)
113-
node.hash_type? && !node.braces?
114-
end
115-
116-
def reserved_method?(method_name)
117-
RuboCop::RSpec::FactoryBot.reserved_methods.include?(method_name)
118-
end
119-
120-
def attribute_defining_method?(method_name)
121-
RuboCop::RSpec::FactoryBot.attribute_defining_methods
122-
.include?(method_name)
123-
end
124-
end
7+
# @!parse
8+
# # Always declare attribute values as blocks.
9+
# #
10+
# # @example
11+
# # # bad
12+
# # kind [:active, :rejected].sample
13+
# #
14+
# # # good
15+
# # kind { [:active, :rejected].sample }
16+
# #
17+
# # # bad
18+
# # closed_at 1.day.from_now
19+
# #
20+
# # # good
21+
# # closed_at { 1.day.from_now }
22+
# #
23+
# # # bad
24+
# # count 1
25+
# #
26+
# # # good
27+
# # count { 1 }
28+
# #
29+
# class AttributeDefinedStatically < ::RuboCop::Cop::Base; end
30+
AttributeDefinedStatically =
31+
::RuboCop::Cop::FactoryBot::AttributeDefinedStatically
12532
end
12633
end
12734
end

lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb

Lines changed: 40 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -4,113 +4,46 @@ module RuboCop
44
module Cop
55
module RSpec
66
module FactoryBot
7-
# Use a consistent style for parentheses in factory bot calls.
8-
#
9-
# @example
10-
#
11-
# # bad
12-
# create :user
13-
# build(:user)
14-
# create(:login)
15-
# create :login
16-
#
17-
# @example `EnforcedStyle: require_parentheses` (default)
18-
#
19-
# # good
20-
# create(:user)
21-
# create(:user)
22-
# create(:login)
23-
# build(:login)
24-
#
25-
# @example `EnforcedStyle: omit_parentheses`
26-
#
27-
# # good
28-
# create :user
29-
# build :user
30-
# create :login
31-
# create :login
32-
#
33-
# # also good
34-
# # when method name and first argument are not on same line
35-
# create(
36-
# :user
37-
# )
38-
# build(
39-
# :user,
40-
# name: 'foo'
41-
# )
42-
#
43-
class ConsistentParenthesesStyle < ::RuboCop::Cop::Base
44-
extend AutoCorrector
45-
include ConfigurableEnforcedStyle
46-
include RuboCop::RSpec::FactoryBot::Language
47-
include RuboCop::Cop::Util
48-
49-
def self.autocorrect_incompatible_with
50-
[Style::MethodCallWithArgsParentheses]
51-
end
52-
53-
MSG_REQUIRE_PARENS = 'Prefer method call with parentheses'
54-
MSG_OMIT_PARENS = 'Prefer method call without parentheses'
55-
56-
FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS
57-
58-
RESTRICT_ON_SEND = FACTORY_CALLS
59-
60-
# @!method factory_call(node)
61-
def_node_matcher :factory_call, <<-PATTERN
62-
(send
63-
{#factory_bot? nil?} %FACTORY_CALLS
64-
{sym str send lvar} _*
65-
)
66-
PATTERN
67-
68-
def on_send(node)
69-
return if ambiguous_without_parentheses?(node)
70-
71-
factory_call(node) do
72-
return if node.method?(:generate) && node.arguments.count > 1
73-
74-
if node.parenthesized?
75-
process_with_parentheses(node)
76-
else
77-
process_without_parentheses(node)
78-
end
79-
end
80-
end
81-
82-
private
83-
84-
def process_with_parentheses(node)
85-
return unless style == :omit_parentheses
86-
return unless same_line?(node, node.first_argument)
87-
88-
add_offense(node.loc.selector,
89-
message: MSG_OMIT_PARENS) do |corrector|
90-
remove_parentheses(corrector, node)
91-
end
92-
end
93-
94-
def process_without_parentheses(node)
95-
return unless style == :require_parentheses
96-
97-
add_offense(node.loc.selector,
98-
message: MSG_REQUIRE_PARENS) do |corrector|
99-
add_parentheses(node, corrector)
100-
end
101-
end
102-
103-
AMBIGUOUS_TYPES = %i[send pair array and or if].freeze
104-
105-
def ambiguous_without_parentheses?(node)
106-
node.parent && AMBIGUOUS_TYPES.include?(node.parent.type)
107-
end
108-
109-
def remove_parentheses(corrector, node)
110-
corrector.replace(node.location.begin, ' ')
111-
corrector.remove(node.location.end)
112-
end
113-
end
7+
# @!parse
8+
# # Use a consistent style for parentheses in factory bot calls.
9+
# #
10+
# # @example
11+
# #
12+
# # # bad
13+
# # create :user
14+
# # build(:user)
15+
# # create(:login)
16+
# # create :login
17+
# #
18+
# # @example `EnforcedStyle: require_parentheses` (default)
19+
# #
20+
# # # good
21+
# # create(:user)
22+
# # create(:user)
23+
# # create(:login)
24+
# # build(:login)
25+
# #
26+
# # @example `EnforcedStyle: omit_parentheses`
27+
# #
28+
# # # good
29+
# # create :user
30+
# # build :user
31+
# # create :login
32+
# # create :login
33+
# #
34+
# # # also good
35+
# # # when method name and first argument are not on same line
36+
# # create(
37+
# # :user
38+
# # )
39+
# # build(
40+
# # :user,
41+
# # name: 'foo'
42+
# # )
43+
# #
44+
# class ConsistentParenthesesStyle < ::RuboCop::Cop::Base; end
45+
ConsistentParenthesesStyle =
46+
::RuboCop::Cop::FactoryBot::ConsistentParenthesesStyle
11447
end
11548
end
11649
end

0 commit comments

Comments
 (0)