rubocop-factory_bot 2.22.0 → 2.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +4 -4
- data/config/default.yml +38 -3
- data/lib/rubocop/cop/factory_bot/association_style.rb +196 -0
- data/lib/rubocop/cop/factory_bot/attribute_defined_statically.rb +4 -4
- data/lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb +52 -38
- data/lib/rubocop/cop/factory_bot/create_list.rb +112 -27
- data/lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb +63 -0
- data/lib/rubocop/cop/factory_bot/factory_name_style.rb +25 -2
- data/lib/rubocop/cop/factory_bot/mixin/configurable_explicit_only.rb +22 -0
- data/lib/rubocop/cop/factory_bot/redundant_factory_option.rb +69 -0
- data/lib/rubocop/cop/factory_bot_cops.rb +3 -0
- data/lib/rubocop/factory_bot/factory_bot.rb +1 -1
- data/lib/rubocop/factory_bot/language.rb +1 -1
- data/lib/rubocop/factory_bot/version.rb +2 -2
- data/lib/rubocop-factory_bot.rb +2 -0
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d8a45a48d47318e9e16e7b3bbde99ae9dcf94f6e90c068f0916ff2945511e85
|
4
|
+
data.tar.gz: 52ff0ffe8fb20bc0ed3ec4dca29aa13a29b106e39f7bacac370388f0c789bf1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53082b6baa7e6ebe69f3ebcc1404f2c8421bfcd3ee6905eba12789faca59c1c090b84abc4696a0773598ab06eb97e8aa2b70384864376b58e568d9eef5ce2549
|
7
|
+
data.tar.gz: 26f1817b168218b61f7657400f777ec5eb972cedab14501b2f6a5b1bd8691af9b4c3b04607969923eb58ffca49e4070622040950a7d00742624d4d5b03cf9ee1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
## Master (Unreleased)
|
4
4
|
|
5
|
+
## 2.23.1 (2023-05-15)
|
6
|
+
|
7
|
+
- Fix `FactoryBot/AssociationStyle` cop for a blockless `factory`. ([@pirj])
|
8
|
+
|
9
|
+
## 2.23.0 (2023-05-15)
|
10
|
+
|
11
|
+
- Add `FactoryBot/FactoryAssociationWithStrategy` cop. ([@morissetcl])
|
12
|
+
- Mark `FactoryBot/CreateList` as `SafeAutoCorrect: false`. ([@r7kamura])
|
13
|
+
- Change `FactoryBot/CreateList` so that it considers `times.map`. ([@r7kamura])
|
14
|
+
- Add `FactoryBot/RedundantFactoryOption` cop. ([@r7kamura])
|
15
|
+
- Add `ExplicitOnly` configuration option to `FactoryBot/ConsistentParenthesesStyle`, `FactoryBot/CreateList` and `FactoryBot/FactoryNameStyle`. ([@ydah])
|
16
|
+
- Change `FactoryBot/CreateList` so that it checks same factory calls in an Array. ([@r7kamura])
|
17
|
+
- Add `FactoryBot/AssociationStyle` cop. ([@r7kamura])
|
18
|
+
|
5
19
|
## 2.22.0 (2023-05-04)
|
6
20
|
|
7
21
|
- Extracted from `rubocop-rspec` into a separate repository for easier use with Minitest/Cucumber. ([@ydah])
|
@@ -57,6 +71,7 @@
|
|
57
71
|
[@jonatas]: https://github.com/jonatas
|
58
72
|
[@leoarnold]: https://github.com/leoarnold
|
59
73
|
[@liberatys]: https://github.com/Liberatys
|
74
|
+
[@morissetcl]: https://github.com/morissetcl
|
60
75
|
[@ngouy]: https://github.com/ngouy
|
61
76
|
[@pirj]: https://github.com/pirj
|
62
77
|
[@r7kamura]: https://github.com/r7kamura
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://rubygems.org/gems/rubocop-factory_bot)
|
5
5
|

|
6
6
|
|
7
|
-
[
|
7
|
+
[factory_bot](https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md)-specific analysis for your projects, as an extension to
|
8
8
|
[RuboCop](https://github.com/rubocop/rubocop).
|
9
9
|
|
10
10
|
## Installation
|
@@ -17,13 +17,13 @@ gem install rubocop-factory_bot
|
|
17
17
|
|
18
18
|
or if you use bundler put this in your `Gemfile`
|
19
19
|
|
20
|
-
```
|
20
|
+
```ruby
|
21
21
|
gem 'rubocop-factory_bot', require: false
|
22
22
|
```
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
You need to tell RuboCop to load the
|
26
|
+
You need to tell RuboCop to load the factory_bot extension. There are three
|
27
27
|
ways to do this:
|
28
28
|
|
29
29
|
### RuboCop configuration file
|
@@ -69,7 +69,7 @@ All cops are located under
|
|
69
69
|
[`lib/rubocop/cop/factory_bot`](lib/rubocop/cop/factory_bot), and contain
|
70
70
|
examples/documentation.
|
71
71
|
|
72
|
-
In your `.rubocop.yml`, you may treat the
|
72
|
+
In your `.rubocop.yml`, you may treat the factory_bot cops just like any other
|
73
73
|
cop. For example:
|
74
74
|
|
75
75
|
```yaml
|
data/config/default.yml
CHANGED
@@ -8,6 +8,22 @@ FactoryBot:
|
|
8
8
|
- "**/features/support/factories/**/*.rb"
|
9
9
|
DocumentationBaseURL: https://docs.rubocop.org/rubocop-factory_bot
|
10
10
|
|
11
|
+
FactoryBot/AssociationStyle:
|
12
|
+
Description: Use a consistent style to define associations.
|
13
|
+
Enabled: pending
|
14
|
+
Safe: false
|
15
|
+
Include:
|
16
|
+
- spec/factories.rb
|
17
|
+
- spec/factories/**/*.rb
|
18
|
+
- features/support/factories/**/*.rb
|
19
|
+
VersionAdded: '2.23'
|
20
|
+
EnforcedStyle: implicit
|
21
|
+
SupportedStyles:
|
22
|
+
- explicit
|
23
|
+
- implicit
|
24
|
+
NonImplicitAssociationMethodNames: ~
|
25
|
+
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/AssociationStyle
|
26
|
+
|
11
27
|
FactoryBot/AttributeDefinedStatically:
|
12
28
|
Description: Always declare attribute values as blocks.
|
13
29
|
Enabled: true
|
@@ -20,13 +36,15 @@ FactoryBot/AttributeDefinedStatically:
|
|
20
36
|
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/AttributeDefinedStatically
|
21
37
|
|
22
38
|
FactoryBot/ConsistentParenthesesStyle:
|
23
|
-
Description: Use a consistent style for parentheses in
|
39
|
+
Description: Use a consistent style for parentheses in factory_bot calls.
|
24
40
|
Enabled: pending
|
25
41
|
EnforcedStyle: require_parentheses
|
26
42
|
SupportedStyles:
|
27
43
|
- require_parentheses
|
28
44
|
- omit_parentheses
|
45
|
+
ExplicitOnly: false
|
29
46
|
VersionAdded: '2.14'
|
47
|
+
VersionChanged: '2.23'
|
30
48
|
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/ConsistentParenthesesStyle
|
31
49
|
|
32
50
|
FactoryBot/CreateList:
|
@@ -42,10 +60,19 @@ FactoryBot/CreateList:
|
|
42
60
|
SupportedStyles:
|
43
61
|
- create_list
|
44
62
|
- n_times
|
63
|
+
ExplicitOnly: false
|
64
|
+
SafeAutoCorrect: false
|
45
65
|
VersionAdded: '1.25'
|
46
|
-
VersionChanged: '2.
|
66
|
+
VersionChanged: '2.23'
|
47
67
|
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/CreateList
|
48
68
|
|
69
|
+
FactoryBot/FactoryAssociationWithStrategy:
|
70
|
+
Description: Use definition in factory association instead of hard coding a strategy.
|
71
|
+
Enabled: pending
|
72
|
+
VersionAdded: '2.23'
|
73
|
+
VersionChanged: '2.23'
|
74
|
+
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryAssociationWithStrategy
|
75
|
+
|
49
76
|
FactoryBot/FactoryClassName:
|
50
77
|
Description: Use string value when setting the class attribute explicitly.
|
51
78
|
Enabled: true
|
@@ -60,13 +87,21 @@ FactoryBot/FactoryClassName:
|
|
60
87
|
FactoryBot/FactoryNameStyle:
|
61
88
|
Description: Checks for name style for argument of FactoryBot::Syntax::Methods.
|
62
89
|
Enabled: pending
|
63
|
-
VersionAdded: '2.16'
|
64
90
|
EnforcedStyle: symbol
|
65
91
|
SupportedStyles:
|
66
92
|
- symbol
|
67
93
|
- string
|
94
|
+
ExplicitOnly: false
|
95
|
+
VersionAdded: '2.16'
|
96
|
+
VersionChanged: '2.23'
|
68
97
|
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/FactoryNameStyle
|
69
98
|
|
99
|
+
FactoryBot/RedundantFactoryOption:
|
100
|
+
Description: Checks for redundant `factory` option.
|
101
|
+
Enabled: pending
|
102
|
+
VersionAdded: '2.23'
|
103
|
+
Reference: https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/FactoryBot/RedundantFactoryOption
|
104
|
+
|
70
105
|
FactoryBot/SyntaxMethods:
|
71
106
|
Description: Use shorthands from `FactoryBot::Syntax::Methods` in your specs.
|
72
107
|
Enabled: pending
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Use a consistent style to define associations.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# This cop may cause false-positives in `EnforcedStyle: explicit`
|
10
|
+
# case. It recognizes any method call that has no arguments as an
|
11
|
+
# implicit association but it might be a user-defined trait call.
|
12
|
+
#
|
13
|
+
# @example EnforcedStyle: implicit (default)
|
14
|
+
# # bad
|
15
|
+
# factory :post do
|
16
|
+
# association :user
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# factory :post do
|
21
|
+
# user
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example EnforcedStyle: explicit
|
25
|
+
# # bad
|
26
|
+
# factory :post do
|
27
|
+
# user
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # good
|
31
|
+
# factory :post do
|
32
|
+
# association :user
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # good (NonImplicitAssociationMethodNames: ['email'])
|
36
|
+
# sequence :email do |n|
|
37
|
+
# "person#{n}@example.com"
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# factory :user do
|
41
|
+
# email
|
42
|
+
# end
|
43
|
+
class AssociationStyle < ::RuboCop::Cop::Base # rubocop:disable Metrics/ClassLength
|
44
|
+
extend AutoCorrector
|
45
|
+
|
46
|
+
include ConfigurableEnforcedStyle
|
47
|
+
|
48
|
+
DEFAULT_NON_IMPLICIT_ASSOCIATION_METHOD_NAMES = %w[
|
49
|
+
association
|
50
|
+
sequence
|
51
|
+
skip_create
|
52
|
+
traits_for_enum
|
53
|
+
].freeze
|
54
|
+
|
55
|
+
RESTRICT_ON_SEND = %i[factory trait].freeze
|
56
|
+
|
57
|
+
def on_send(node)
|
58
|
+
bad_associations_in(node).each do |association|
|
59
|
+
add_offense(
|
60
|
+
association,
|
61
|
+
message: "Use #{style} style to define associations."
|
62
|
+
) do |corrector|
|
63
|
+
autocorrect(corrector, association)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# @!method explicit_association?(node)
|
71
|
+
def_node_matcher :explicit_association?, <<~PATTERN
|
72
|
+
(send nil? :association sym ...)
|
73
|
+
PATTERN
|
74
|
+
|
75
|
+
# @!method implicit_association?(node)
|
76
|
+
def_node_matcher :implicit_association?, <<~PATTERN
|
77
|
+
(send nil? !#non_implicit_association_method_name? ...)
|
78
|
+
PATTERN
|
79
|
+
|
80
|
+
# @!method factory_option_matcher(node)
|
81
|
+
def_node_matcher :factory_option_matcher, <<~PATTERN
|
82
|
+
(send
|
83
|
+
nil?
|
84
|
+
:association
|
85
|
+
...
|
86
|
+
(hash
|
87
|
+
<
|
88
|
+
(pair
|
89
|
+
(sym :factory)
|
90
|
+
{
|
91
|
+
(sym $_) |
|
92
|
+
(array (sym $_)*)
|
93
|
+
}
|
94
|
+
)
|
95
|
+
...
|
96
|
+
>
|
97
|
+
)
|
98
|
+
)
|
99
|
+
PATTERN
|
100
|
+
|
101
|
+
# @!method trait_names_from_explicit(node)
|
102
|
+
def_node_matcher :trait_names_from_explicit, <<~PATTERN
|
103
|
+
(send nil? :association _ (sym $_)* ...)
|
104
|
+
PATTERN
|
105
|
+
|
106
|
+
def autocorrect(corrector, node)
|
107
|
+
if style == :explicit
|
108
|
+
autocorrect_to_explicit_style(corrector, node)
|
109
|
+
else
|
110
|
+
autocorrect_to_implicit_style(corrector, node)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def autocorrect_to_explicit_style(corrector, node)
|
115
|
+
arguments = [
|
116
|
+
":#{node.method_name}",
|
117
|
+
*node.arguments.map(&:source)
|
118
|
+
]
|
119
|
+
corrector.replace(node, "association #{arguments.join(', ')}")
|
120
|
+
end
|
121
|
+
|
122
|
+
def autocorrect_to_implicit_style(corrector, node)
|
123
|
+
source = node.first_argument.value.to_s
|
124
|
+
options = options_for_autocorrect_to_implicit_style(node)
|
125
|
+
unless options.empty?
|
126
|
+
rest = options.map { |option| option.join(': ') }.join(', ')
|
127
|
+
source += " #{rest}"
|
128
|
+
end
|
129
|
+
corrector.replace(node, source)
|
130
|
+
end
|
131
|
+
|
132
|
+
def bad?(node)
|
133
|
+
if style == :explicit
|
134
|
+
implicit_association?(node)
|
135
|
+
else
|
136
|
+
explicit_association?(node)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def bad_associations_in(node)
|
141
|
+
children_of_factory_block(node).select do |child|
|
142
|
+
bad?(child)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def children_of_factory_block(node)
|
147
|
+
block = node.block_node
|
148
|
+
return [] unless block
|
149
|
+
return [] unless block.body
|
150
|
+
|
151
|
+
if block.body.begin_type?
|
152
|
+
block.body.children
|
153
|
+
else
|
154
|
+
[block.body]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def factory_names_from_explicit(node)
|
159
|
+
trait_names = trait_names_from_explicit(node)
|
160
|
+
factory_names = Array(factory_option_matcher(node))
|
161
|
+
result = factory_names + trait_names
|
162
|
+
if factory_names.empty? && !trait_names.empty?
|
163
|
+
result.prepend(node.first_argument.value)
|
164
|
+
end
|
165
|
+
result
|
166
|
+
end
|
167
|
+
|
168
|
+
def non_implicit_association_method_name?(method_name)
|
169
|
+
non_implicit_association_method_names.include?(method_name.to_s)
|
170
|
+
end
|
171
|
+
|
172
|
+
def non_implicit_association_method_names
|
173
|
+
DEFAULT_NON_IMPLICIT_ASSOCIATION_METHOD_NAMES +
|
174
|
+
(cop_config['NonImplicitAssociationMethodNames'] || [])
|
175
|
+
end
|
176
|
+
|
177
|
+
def options_from_explicit(node)
|
178
|
+
return {} unless node.last_argument.hash_type?
|
179
|
+
|
180
|
+
node.last_argument.pairs.inject({}) do |options, pair|
|
181
|
+
options.merge(pair.key.value => pair.value.source)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def options_for_autocorrect_to_implicit_style(node)
|
186
|
+
options = options_from_explicit(node)
|
187
|
+
factory_names = factory_names_from_explicit(node)
|
188
|
+
unless factory_names.empty?
|
189
|
+
options[:factory] = "%i[#{factory_names.join(' ')}]"
|
190
|
+
end
|
191
|
+
options
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -30,13 +30,13 @@ module RuboCop
|
|
30
30
|
MSG = 'Use a block to declare attribute values.'
|
31
31
|
|
32
32
|
# @!method value_matcher(node)
|
33
|
-
def_node_matcher :value_matcher,
|
34
|
-
|
33
|
+
def_node_matcher :value_matcher, <<~PATTERN
|
34
|
+
(send _ !#reserved_method? $...)
|
35
35
|
PATTERN
|
36
36
|
|
37
37
|
# @!method factory_attributes(node)
|
38
|
-
def_node_matcher :factory_attributes,
|
39
|
-
|
38
|
+
def_node_matcher :factory_attributes, <<~PATTERN
|
39
|
+
(block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } )
|
40
40
|
PATTERN
|
41
41
|
|
42
42
|
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
@@ -3,31 +3,27 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module FactoryBot
|
6
|
-
# Use a consistent style for parentheses in
|
6
|
+
# Use a consistent style for parentheses in factory_bot calls.
|
7
7
|
#
|
8
|
-
# @example
|
8
|
+
# @example `EnforcedStyle: require_parentheses` (default)
|
9
9
|
#
|
10
10
|
# # bad
|
11
11
|
# create :user
|
12
|
-
# build
|
13
|
-
# create(:login)
|
14
|
-
# create :login
|
15
|
-
#
|
16
|
-
# @example `EnforcedStyle: require_parentheses` (default)
|
12
|
+
# build :login
|
17
13
|
#
|
18
14
|
# # good
|
19
15
|
# create(:user)
|
20
|
-
# create(:user)
|
21
|
-
# create(:login)
|
22
16
|
# build(:login)
|
23
17
|
#
|
24
18
|
# @example `EnforcedStyle: omit_parentheses`
|
25
19
|
#
|
20
|
+
# # bad
|
21
|
+
# create(:user)
|
22
|
+
# build(:login)
|
23
|
+
#
|
26
24
|
# # good
|
27
25
|
# create :user
|
28
|
-
# build :
|
29
|
-
# create :login
|
30
|
-
# create :login
|
26
|
+
# build :login
|
31
27
|
#
|
32
28
|
# # also good
|
33
29
|
# # when method name and first argument are not on same line
|
@@ -39,49 +35,67 @@ module RuboCop
|
|
39
35
|
# name: 'foo'
|
40
36
|
# )
|
41
37
|
#
|
38
|
+
# @example `ExplicitOnly: false` (default)
|
39
|
+
#
|
40
|
+
# # bad - with `EnforcedStyle: require_parentheses`
|
41
|
+
# FactoryBot.create :user
|
42
|
+
# build :user
|
43
|
+
#
|
44
|
+
# # good - with `EnforcedStyle: require_parentheses`
|
45
|
+
# FactoryBot.create(:user)
|
46
|
+
# build(:user)
|
47
|
+
#
|
48
|
+
# @example `ExplicitOnly: true`
|
49
|
+
#
|
50
|
+
# # bad - with `EnforcedStyle: require_parentheses`
|
51
|
+
# FactoryBot.create :user
|
52
|
+
# FactoryBot.build :user
|
53
|
+
#
|
54
|
+
# # good - with `EnforcedStyle: require_parentheses`
|
55
|
+
# FactoryBot.create(:user)
|
56
|
+
# FactoryBot.build(:user)
|
57
|
+
# create :user
|
58
|
+
# build :user
|
59
|
+
#
|
42
60
|
class ConsistentParenthesesStyle < ::RuboCop::Cop::Base
|
43
61
|
extend AutoCorrector
|
44
62
|
include ConfigurableEnforcedStyle
|
45
|
-
include
|
46
|
-
include RuboCop::Cop::Util
|
47
|
-
|
48
|
-
def self.autocorrect_incompatible_with
|
49
|
-
[Style::MethodCallWithArgsParentheses]
|
50
|
-
end
|
63
|
+
include ConfigurableExplicitOnly
|
51
64
|
|
52
65
|
MSG_REQUIRE_PARENS = 'Prefer method call with parentheses'
|
53
66
|
MSG_OMIT_PARENS = 'Prefer method call without parentheses'
|
54
|
-
|
55
67
|
FACTORY_CALLS = RuboCop::FactoryBot::Language::METHODS
|
56
|
-
|
57
68
|
RESTRICT_ON_SEND = FACTORY_CALLS
|
58
69
|
|
59
70
|
# @!method factory_call(node)
|
60
|
-
def_node_matcher :factory_call,
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
71
|
+
def_node_matcher :factory_call, <<~PATTERN
|
72
|
+
(send
|
73
|
+
#factory_call? %FACTORY_CALLS
|
74
|
+
{sym str send lvar} _*
|
75
|
+
)
|
65
76
|
PATTERN
|
66
77
|
|
78
|
+
def self.autocorrect_incompatible_with
|
79
|
+
[Style::MethodCallWithArgsParentheses]
|
80
|
+
end
|
81
|
+
|
67
82
|
def on_send(node)
|
68
83
|
return if ambiguous_without_parentheses?(node)
|
69
84
|
|
70
|
-
factory_call(node)
|
71
|
-
return if node.method?(:generate) && node.arguments.count > 1
|
72
|
-
|
73
|
-
if node.parenthesized?
|
74
|
-
process_with_parentheses(node)
|
75
|
-
else
|
76
|
-
process_without_parentheses(node)
|
77
|
-
end
|
78
|
-
end
|
85
|
+
factory_call(node) { register_offense(node) }
|
79
86
|
end
|
80
87
|
|
81
88
|
private
|
82
89
|
|
83
|
-
def
|
84
|
-
return
|
90
|
+
def register_offense(node)
|
91
|
+
return if node.method?(:generate) && node.arguments.count > 1
|
92
|
+
|
93
|
+
register_offense_with_parentheses(node)
|
94
|
+
register_offense_without_parentheses(node)
|
95
|
+
end
|
96
|
+
|
97
|
+
def register_offense_with_parentheses(node)
|
98
|
+
return if style == :require_parentheses || !node.parenthesized?
|
85
99
|
return unless same_line?(node, node.first_argument)
|
86
100
|
|
87
101
|
add_offense(node.loc.selector,
|
@@ -90,8 +104,8 @@ module RuboCop
|
|
90
104
|
end
|
91
105
|
end
|
92
106
|
|
93
|
-
def
|
94
|
-
return
|
107
|
+
def register_offense_without_parentheses(node)
|
108
|
+
return if style == :omit_parentheses || node.parenthesized?
|
95
109
|
|
96
110
|
add_offense(node.loc.selector,
|
97
111
|
message: MSG_REQUIRE_PARENS) do |corrector|
|
@@ -7,9 +7,16 @@ module RuboCop
|
|
7
7
|
#
|
8
8
|
# This cop can be configured using the `EnforcedStyle` option
|
9
9
|
#
|
10
|
+
# @safety
|
11
|
+
# This cop's autocorrection is unsafe because replacing `n.times` to
|
12
|
+
# `create_list` changes its returned value.
|
13
|
+
#
|
10
14
|
# @example `EnforcedStyle: create_list` (default)
|
11
15
|
# # bad
|
12
16
|
# 3.times { create :user }
|
17
|
+
# 3.times.map { create :user }
|
18
|
+
# [create(:user), create(:user), create(:user)]
|
19
|
+
# Array.new(3) { create :user }
|
13
20
|
#
|
14
21
|
# # good
|
15
22
|
# create_list :user, 3
|
@@ -26,58 +33,105 @@ module RuboCop
|
|
26
33
|
# @example `EnforcedStyle: n_times`
|
27
34
|
# # bad
|
28
35
|
# create_list :user, 3
|
36
|
+
# [create(:user), create(:user), create(:user)]
|
29
37
|
#
|
30
38
|
# # good
|
39
|
+
# 3.times.map { create :user }
|
40
|
+
#
|
41
|
+
# @example `ExplicitOnly: false` (default)
|
42
|
+
#
|
43
|
+
# # bad - with `EnforcedStyle: create_list`
|
44
|
+
# 3.times { FactoryBot.create :user }
|
45
|
+
# 3.times { create :user }
|
46
|
+
#
|
47
|
+
# # good - with `EnforcedStyle: create_list`
|
48
|
+
# FactoryBot.create_list :user, 3
|
49
|
+
# create_list :user, 3
|
50
|
+
#
|
51
|
+
# @example `ExplicitOnly: true`
|
52
|
+
#
|
53
|
+
# # bad - with `EnforcedStyle: create_list`
|
54
|
+
# 3.times { FactoryBot.create :user }
|
55
|
+
#
|
56
|
+
# # good - with `EnforcedStyle: create_list`
|
57
|
+
# FactoryBot.create_list :user, 3
|
58
|
+
# create_list :user, 3
|
31
59
|
# 3.times { create :user }
|
32
60
|
#
|
33
61
|
class CreateList < ::RuboCop::Cop::Base
|
34
62
|
extend AutoCorrector
|
35
63
|
include ConfigurableEnforcedStyle
|
36
64
|
include RuboCop::FactoryBot::Language
|
65
|
+
include ConfigurableExplicitOnly
|
37
66
|
|
38
67
|
MSG_CREATE_LIST = 'Prefer create_list.'
|
39
|
-
MSG_N_TIMES = 'Prefer %<number>s.times.'
|
68
|
+
MSG_N_TIMES = 'Prefer %<number>s.times.map.'
|
40
69
|
RESTRICT_ON_SEND = %i[create_list].freeze
|
41
70
|
|
42
|
-
# @!method
|
43
|
-
def_node_matcher :
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
71
|
+
# @!method repetition_block?(node)
|
72
|
+
def_node_matcher :repetition_block?, <<-PATTERN
|
73
|
+
(block {#array_new? #n_times? #n_times_map?} ...)
|
74
|
+
PATTERN
|
75
|
+
|
76
|
+
# @!method array_new?(node)
|
77
|
+
def_node_matcher :array_new?, <<-PATTERN
|
78
|
+
(send (const {nil? cbase} :Array) :new (int _))
|
79
|
+
PATTERN
|
80
|
+
|
81
|
+
# @!method n_times?(node)
|
82
|
+
def_node_matcher :n_times?, <<-PATTERN
|
83
|
+
(send (int _) :times)
|
84
|
+
PATTERN
|
85
|
+
|
86
|
+
# @!method n_times_map?(node)
|
87
|
+
def_node_matcher :n_times_map?, <<-PATTERN
|
88
|
+
(send #n_times? :map)
|
51
89
|
PATTERN
|
52
90
|
|
53
91
|
# @!method block_with_arg_and_used?(node)
|
54
|
-
def_node_matcher :block_with_arg_and_used?,
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
92
|
+
def_node_matcher :block_with_arg_and_used?, <<~PATTERN
|
93
|
+
(block
|
94
|
+
_
|
95
|
+
(args (arg _value))
|
96
|
+
`_value
|
97
|
+
)
|
60
98
|
PATTERN
|
61
99
|
|
62
100
|
# @!method arguments_include_method_call?(node)
|
63
|
-
def_node_matcher :arguments_include_method_call?,
|
64
|
-
|
101
|
+
def_node_matcher :arguments_include_method_call?, <<~PATTERN
|
102
|
+
(send #factory_call? :create sym `$(send ...))
|
65
103
|
PATTERN
|
66
104
|
|
67
105
|
# @!method factory_call(node)
|
68
|
-
def_node_matcher :factory_call,
|
69
|
-
|
106
|
+
def_node_matcher :factory_call, <<~PATTERN
|
107
|
+
(send #factory_call? :create sym ...)
|
70
108
|
PATTERN
|
71
109
|
|
72
110
|
# @!method factory_list_call(node)
|
73
|
-
def_node_matcher :factory_list_call,
|
74
|
-
|
111
|
+
def_node_matcher :factory_list_call, <<~PATTERN
|
112
|
+
(send #factory_call? :create_list (sym _) (int $_) ...)
|
75
113
|
PATTERN
|
76
114
|
|
115
|
+
# @!method factory_calls_in_array?(node)
|
116
|
+
def_node_search :factory_calls_in_array?, <<-PATTERN
|
117
|
+
(array #factory_call+)
|
118
|
+
PATTERN
|
119
|
+
|
120
|
+
def on_array(node)
|
121
|
+
return unless same_factory_calls_in_array?(node)
|
122
|
+
|
123
|
+
add_offense(
|
124
|
+
node,
|
125
|
+
message: preferred_message_for_array(node)
|
126
|
+
) do |corrector|
|
127
|
+
autocorrect_same_factory_calls_in_array(corrector, node)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
77
131
|
def on_block(node) # rubocop:todo InternalAffairs/NumblockHandler
|
78
132
|
return unless style == :create_list
|
79
133
|
|
80
|
-
return unless
|
134
|
+
return unless repetition_block?(node)
|
81
135
|
return if block_with_arg_and_used?(node)
|
82
136
|
return unless node.body
|
83
137
|
return if arguments_include_method_call?(node.body)
|
@@ -101,6 +155,20 @@ module RuboCop
|
|
101
155
|
|
102
156
|
private
|
103
157
|
|
158
|
+
# For ease of modification, it is replaced with the `n_times` style,
|
159
|
+
# but if it is not appropriate for the configured style,
|
160
|
+
# it will be replaced in the subsequent autocorrection.
|
161
|
+
def autocorrect_same_factory_calls_in_array(corrector, node)
|
162
|
+
corrector.replace(
|
163
|
+
node,
|
164
|
+
format(
|
165
|
+
'%<count>s.times.map { %<factory_call>s }',
|
166
|
+
count: node.children.count,
|
167
|
+
factory_call: node.children.first.source
|
168
|
+
)
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
104
172
|
def contains_only_factory?(node)
|
105
173
|
if node.block_type?
|
106
174
|
factory_call(node.send_node)
|
@@ -109,6 +177,20 @@ module RuboCop
|
|
109
177
|
end
|
110
178
|
end
|
111
179
|
|
180
|
+
def preferred_message_for_array(node)
|
181
|
+
if style == :create_list &&
|
182
|
+
!arguments_include_method_call?(node.children.first)
|
183
|
+
MSG_CREATE_LIST
|
184
|
+
else
|
185
|
+
format(MSG_N_TIMES, number: node.children.count)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def same_factory_calls_in_array?(node)
|
190
|
+
factory_calls_in_array?(node) &&
|
191
|
+
node.children.map(&:source).uniq.one?
|
192
|
+
end
|
193
|
+
|
112
194
|
# :nodoc
|
113
195
|
module Corrector
|
114
196
|
private
|
@@ -159,7 +241,7 @@ module RuboCop
|
|
159
241
|
replacement = format_receiver(node.receiver)
|
160
242
|
replacement += format_method_call(node, 'create', arguments)
|
161
243
|
replacement += " #{factory_call_block_source}" if node.block_node
|
162
|
-
"#{count.source}.times { #{replacement} }"
|
244
|
+
"#{count.source}.times.map { #{replacement} }"
|
163
245
|
end
|
164
246
|
|
165
247
|
def factory_call_block_source
|
@@ -224,10 +306,13 @@ module RuboCop
|
|
224
306
|
|
225
307
|
def count_from(node)
|
226
308
|
count_node =
|
227
|
-
|
228
|
-
|
229
|
-
|
309
|
+
case node.method_name
|
310
|
+
when :map
|
311
|
+
node.receiver.receiver
|
312
|
+
when :new
|
230
313
|
node.send_node.first_argument
|
314
|
+
when :times
|
315
|
+
node.receiver
|
231
316
|
end
|
232
317
|
count_node.source
|
233
318
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Use definition in factory association instead of hard coding a strategy.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad - only works for one strategy
|
10
|
+
# factory :foo do
|
11
|
+
# profile { create(:profile) }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # good - implicit
|
15
|
+
# factory :foo do
|
16
|
+
# profile
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good - explicit
|
20
|
+
# factory :foo do
|
21
|
+
# association :profile
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # good - inline
|
25
|
+
# factory :foo do
|
26
|
+
# profile { association :profile }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
class FactoryAssociationWithStrategy < ::RuboCop::Cop::Base
|
30
|
+
MSG = 'Use an implicit, explicit or inline definition instead of ' \
|
31
|
+
'hard coding a strategy for setting association within factory.'
|
32
|
+
|
33
|
+
HARDCODED = Set.new(%i[create build build_stubbed]).freeze
|
34
|
+
|
35
|
+
# @!method factory_declaration(node)
|
36
|
+
def_node_matcher :factory_declaration, <<~PATTERN
|
37
|
+
(block (send nil? {:factory :trait} ...)
|
38
|
+
...
|
39
|
+
)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
# @!method factory_strategy_association(node)
|
43
|
+
def_node_matcher :factory_strategy_association, <<~PATTERN
|
44
|
+
(block
|
45
|
+
(send nil? _association_name)
|
46
|
+
(args)
|
47
|
+
< $(send nil? HARDCODED ...) ... >
|
48
|
+
)
|
49
|
+
PATTERN
|
50
|
+
|
51
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
52
|
+
factory_declaration(node) do
|
53
|
+
node.each_node do |statement|
|
54
|
+
factory_strategy_association(statement) do |hardcoded_association|
|
55
|
+
add_offense(hardcoded_association)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -23,19 +23,42 @@ module RuboCop
|
|
23
23
|
# create('user')
|
24
24
|
# build "user", username: "NAME"
|
25
25
|
#
|
26
|
+
# @example `ExplicitOnly: false` (default)
|
27
|
+
#
|
28
|
+
# # bad - with `EnforcedStyle: symbol`
|
29
|
+
# FactoryBot.create('user')
|
30
|
+
# create('user')
|
31
|
+
#
|
32
|
+
# # good - with `EnforcedStyle: symbol`
|
33
|
+
# FactoryBot.create(:user)
|
34
|
+
# create(:user)
|
35
|
+
#
|
36
|
+
# @example `ExplicitOnly: true`
|
37
|
+
#
|
38
|
+
# # bad - with `EnforcedStyle: symbol`
|
39
|
+
# FactoryBot.create(:user)
|
40
|
+
# FactoryBot.build "user", username: "NAME"
|
41
|
+
#
|
42
|
+
# # good - with `EnforcedStyle: symbol`
|
43
|
+
# FactoryBot.create('user')
|
44
|
+
# FactoryBot.build "user", username: "NAME"
|
45
|
+
# FactoryBot.create(:user)
|
46
|
+
# create(:user)
|
47
|
+
#
|
26
48
|
class FactoryNameStyle < ::RuboCop::Cop::Base
|
27
49
|
extend AutoCorrector
|
28
50
|
include ConfigurableEnforcedStyle
|
29
51
|
include RuboCop::FactoryBot::Language
|
52
|
+
include ConfigurableExplicitOnly
|
30
53
|
|
31
54
|
MSG = 'Use %<prefer>s to refer to a factory.'
|
32
55
|
FACTORY_CALLS = RuboCop::FactoryBot::Language::METHODS
|
33
56
|
RESTRICT_ON_SEND = FACTORY_CALLS
|
34
57
|
|
35
58
|
# @!method factory_call(node)
|
36
|
-
def_node_matcher :factory_call,
|
59
|
+
def_node_matcher :factory_call, <<~PATTERN
|
37
60
|
(send
|
38
|
-
|
61
|
+
#factory_call? %FACTORY_CALLS
|
39
62
|
${str sym} ...
|
40
63
|
)
|
41
64
|
PATTERN
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Handles `ExplicitOnly` configuration parameters.
|
7
|
+
module ConfigurableExplicitOnly
|
8
|
+
include RuboCop::FactoryBot::Language
|
9
|
+
|
10
|
+
def factory_call?(node)
|
11
|
+
return factory_bot?(node) if explicit_only?
|
12
|
+
|
13
|
+
factory_bot?(node) || node.nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def explicit_only?
|
17
|
+
cop_config['ExplicitOnly']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Checks for redundant `factory` option.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# association :user, factory: :user
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# association :user
|
14
|
+
class RedundantFactoryOption < ::RuboCop::Cop::Base
|
15
|
+
extend AutoCorrector
|
16
|
+
|
17
|
+
include RangeHelp
|
18
|
+
|
19
|
+
MSG = 'Remove redundant `factory` option.'
|
20
|
+
|
21
|
+
RESTRICT_ON_SEND = %i[association].freeze
|
22
|
+
|
23
|
+
# @!method association_with_a_factory_option(node)
|
24
|
+
def_node_matcher :association_with_a_factory_option, <<~PATTERN
|
25
|
+
(send nil? :association
|
26
|
+
(sym $_association_name)
|
27
|
+
...
|
28
|
+
(hash
|
29
|
+
<$(pair
|
30
|
+
(sym :factory)
|
31
|
+
{
|
32
|
+
(sym $_factory_name)
|
33
|
+
(array (sym $_factory_name))
|
34
|
+
}
|
35
|
+
)
|
36
|
+
...
|
37
|
+
>
|
38
|
+
)
|
39
|
+
)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def on_send(node)
|
43
|
+
association_with_a_factory_option(node) do
|
44
|
+
|association_name, factory_option, factory_name|
|
45
|
+
next if association_name != factory_name
|
46
|
+
|
47
|
+
add_offense(factory_option) do |corrector|
|
48
|
+
autocorrect(corrector, factory_option)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def autocorrect(corrector, node)
|
56
|
+
corrector.remove(
|
57
|
+
range_with_surrounding_comma(
|
58
|
+
range_with_surrounding_space(
|
59
|
+
node.source_range,
|
60
|
+
side: :left
|
61
|
+
),
|
62
|
+
:left
|
63
|
+
)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'factory_bot/association_style'
|
3
4
|
require_relative 'factory_bot/attribute_defined_statically'
|
4
5
|
require_relative 'factory_bot/consistent_parentheses_style'
|
5
6
|
require_relative 'factory_bot/create_list'
|
7
|
+
require_relative 'factory_bot/factory_association_with_strategy'
|
6
8
|
require_relative 'factory_bot/factory_class_name'
|
7
9
|
require_relative 'factory_bot/factory_name_style'
|
10
|
+
require_relative 'factory_bot/redundant_factory_option'
|
8
11
|
require_relative 'factory_bot/syntax_methods'
|
data/lib/rubocop-factory_bot.rb
CHANGED
@@ -8,6 +8,8 @@ require 'rubocop'
|
|
8
8
|
require_relative 'rubocop/factory_bot/factory_bot'
|
9
9
|
require_relative 'rubocop/factory_bot/language'
|
10
10
|
|
11
|
+
require_relative 'rubocop/cop/factory_bot/mixin/configurable_explicit_only'
|
12
|
+
|
11
13
|
require_relative 'rubocop/cop/factory_bot_cops'
|
12
14
|
|
13
15
|
project_root = File.join(__dir__, '..')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-factory_bot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.23.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Backus
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2023-05-
|
15
|
+
date: 2023-05-16 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: rubocop
|
@@ -28,9 +28,9 @@ dependencies:
|
|
28
28
|
- - "~>"
|
29
29
|
- !ruby/object:Gem::Version
|
30
30
|
version: '1.33'
|
31
|
-
description: |
|
32
|
-
|
33
|
-
|
31
|
+
description: |
|
32
|
+
Code style checking for factory_bot files.
|
33
|
+
A plugin for the RuboCop code style enforcing & linting tool.
|
34
34
|
email:
|
35
35
|
executables: []
|
36
36
|
extensions: []
|
@@ -44,11 +44,15 @@ files:
|
|
44
44
|
- README.md
|
45
45
|
- config/default.yml
|
46
46
|
- lib/rubocop-factory_bot.rb
|
47
|
+
- lib/rubocop/cop/factory_bot/association_style.rb
|
47
48
|
- lib/rubocop/cop/factory_bot/attribute_defined_statically.rb
|
48
49
|
- lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb
|
49
50
|
- lib/rubocop/cop/factory_bot/create_list.rb
|
51
|
+
- lib/rubocop/cop/factory_bot/factory_association_with_strategy.rb
|
50
52
|
- lib/rubocop/cop/factory_bot/factory_class_name.rb
|
51
53
|
- lib/rubocop/cop/factory_bot/factory_name_style.rb
|
54
|
+
- lib/rubocop/cop/factory_bot/mixin/configurable_explicit_only.rb
|
55
|
+
- lib/rubocop/cop/factory_bot/redundant_factory_option.rb
|
52
56
|
- lib/rubocop/cop/factory_bot/syntax_methods.rb
|
53
57
|
- lib/rubocop/cop/factory_bot_cops.rb
|
54
58
|
- lib/rubocop/factory_bot/config_formatter.rb
|
@@ -78,8 +82,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
82
|
- !ruby/object:Gem::Version
|
79
83
|
version: '0'
|
80
84
|
requirements: []
|
81
|
-
rubygems_version: 3.
|
85
|
+
rubygems_version: 3.4.13
|
82
86
|
signing_key:
|
83
87
|
specification_version: 4
|
84
|
-
summary: Code style checking for
|
88
|
+
summary: Code style checking for factory_bot files
|
85
89
|
test_files: []
|