@@ -31,6 +31,21 @@ module FactoryBot
3131 # user factory: %i[user author]
3232 # end
3333 #
34+ # # bad
35+ # factory :post do
36+ # user { association :user }
37+ # end
38+ #
39+ # # good
40+ # factory :post do
41+ # user
42+ # end
43+ #
44+ # # good (NonImplicitAssociationMethodNames: ['foo'])
45+ # factory :post do
46+ # foo { association :foo }
47+ # end
48+ #
3449 # @example `EnforcedStyle: explicit`
3550 # # bad
3651 # factory :post do
@@ -52,6 +67,16 @@ module FactoryBot
5267 # association :user, :author
5368 # end
5469 #
70+ # # bad
71+ # factory :post do
72+ # user { association :user }
73+ # end
74+ #
75+ # # good
76+ # factory :post do
77+ # association :user
78+ # end
79+ #
5580 # # good (NonImplicitAssociationMethodNames: ['email'])
5681 # sequence :email do |n|
5782 # "person#{n}@example.com"
@@ -66,19 +91,22 @@ class AssociationStyle < ::RuboCop::Cop::Base # rubocop:disable Metrics/ClassLen
6691 include ConfigurableEnforcedStyle
6792
6893 RESTRICT_ON_SEND = %i[ factory trait ] . freeze
94+
6995 KEYWORDS = %i[ alias and begin break case class def defined? do
7096 else elsif end ensure false for if in module
7197 next nil not or redo rescue retry return self
7298 super then true undef unless until when while
73- yield __FILE__ __LINE__ __ENCODING__ ] . freeze
99+ yield __FILE__ __LINE__ __ENCODING__ ] . to_set . freeze
74100
75101 def on_send ( node )
76- bad_associations_in ( node ) . each do |association |
102+ body_nodes_from ( node ) . each do |maybe_association |
103+ next unless correctable_to_enforced_style? ( maybe_association )
104+
77105 add_offense (
78- association ,
106+ maybe_association ,
79107 message : "Use #{ style } style to define associations."
80108 ) do |corrector |
81- autocorrect ( corrector , association )
109+ autocorrect ( corrector , maybe_association )
82110 end
83111 end
84112 end
@@ -90,110 +118,118 @@ def on_send(node)
90118 (send nil? :association sym ...)
91119 PATTERN
92120
93- # @!method with_strategy_build_option?(node)
94- def_node_matcher :with_strategy_build_option? , <<~PATTERN
95- (send nil? :association sym ...
96- (hash <(pair (sym :strategy) (sym :build)) ...>)
97- )
121+ # @!method with_strategy_option?(node)
122+ def_node_matcher :with_strategy_option? , <<~PATTERN
123+ (send nil? ... (hash <(pair (sym :strategy) _) ...>))
98124 PATTERN
99125
100- # @!method implicit_association ?(node)
101- def_node_matcher :implicit_association ? , <<~PATTERN
102- (send nil? !#non_implicit_association_method_name? ...)
126+ # @!method receiverless_method_call ?(node)
127+ def_node_matcher :receiverless_method_call ? , <<~PATTERN
128+ (send nil? ...)
103129 PATTERN
104130
105131 # @!method factory_option_matcher(node)
106132 def_node_matcher :factory_option_matcher , <<~PATTERN
107- (send
108- nil?
109- :association
110- ...
111- (hash
112- <
113- (pair
114- (sym :factory)
115- {
116- (sym $_) |
117- (array (sym $_)*)
118- }
119- )
120- ...
121- >
122- )
123- )
133+ (send nil? ... (hash <(pair (sym :factory) {(sym $_) (array (sym $_)*)}) ...>))
124134 PATTERN
125135
126136 # @!method trait_names_from_explicit(node)
127137 def_node_matcher :trait_names_from_explicit , <<~PATTERN
128138 (send nil? :association _ (sym $_)* ...)
129139 PATTERN
130140
131- # @!method association_names(node)
132- def_node_search :association_names , <<~PATTERN
133- (send nil? :association $...)
141+ # @!method search_defined_trait_names_in(node)
142+ def_node_search :search_defined_trait_names_in , <<~PATTERN
143+ (send nil? :trait (sym $_) )
144+ PATTERN
145+
146+ # @!method factory_definition_node?(node)
147+ def_node_matcher :factory_definition_node? , <<~PATTERN
148+ (block (send nil? :factory ...) ...)
134149 PATTERN
135150
136- # @!method trait_name(node)
137- def_node_search :trait_name , <<~PATTERN
138- (send nil? :trait (sym $_) )
151+ # @!method inline_associationish?(node)
152+ def_node_matcher :inline_associationish? , <<~PATTERN
153+ (block
154+ (send nil? _)
155+ (args)
156+ (send nil? :association ...)
157+ )
139158 PATTERN
140159
141- def autocorrect ( corrector , node )
142- if style == :explicit
143- autocorrect_to_explicit_style ( corrector , node )
144- else
145- autocorrect_to_implicit_style ( corrector , node )
160+ def correctable_to_enforced_style? ( node )
161+ case style
162+ when :explicit
163+ correctable_to_explicit_style? ( node )
164+ when :implicit
165+ correctable_to_implicit_style? ( node )
146166 end
147167 end
148168
149- def autocorrect_to_explicit_style ( corrector , node )
150- arguments = [
151- ":#{ node . method_name } " ,
152- *node . arguments . map ( &:source )
153- ]
154- corrector . replace ( node , "association #{ arguments . join ( ', ' ) } " )
169+ def correctable_to_explicit_style? ( node )
170+ if explicit_association? ( node )
171+ false
172+ elsif implicit_association? ( node )
173+ true
174+ elsif inline_association? ( node )
175+ true
176+ end
155177 end
156178
157- def autocorrect_to_implicit_style ( corrector , node )
158- source = node . first_argument . value . to_s
159- options = options_for_autocorrect_to_implicit_style ( node )
160- unless options . empty?
161- rest = options . map { |option | option . join ( ': ' ) } . join ( ', ' )
162- source += " #{ rest } "
179+ def correctable_to_implicit_style? ( node )
180+ if explicit_association? ( node )
181+ !with_strategy_option? ( node ) &&
182+ !keyword_explicit_association_name? ( node )
183+ elsif implicit_association? ( node )
184+ false
185+ elsif inline_association? ( node )
186+ !with_strategy_option? ( node . body )
163187 end
164- corrector . replace ( node , source )
165188 end
166189
167- def bad? ( node )
168- if style == :explicit
169- implicit_association? ( node ) &&
170- ( factory_node = trait_factory_node ( node ) ) && !trait_within_trait? (
171- node , factory_node
172- )
173- else
174- explicit_association? ( node ) &&
175- !with_strategy_build_option? ( node ) &&
176- !keyword? ( node )
177- end
190+ def inline_association? ( node )
191+ inline_associationish? ( node ) &&
192+ implicit_association_method? ( node )
178193 end
179194
180- def keyword ?( node )
181- association_names ( node ) . any? do | associations |
182- associations . any? do | association |
183- next unless association . sym_type?
195+ def implicit_association ?( node )
196+ receiverless_method_call? ( node ) &&
197+ implicit_association_method? ( node )
198+ end
184199
185- KEYWORDS . include? ( association . value )
186- end
187- end
200+ def implicit_association_method? ( node )
201+ ! non_implicit_association_method_names_for ( node )
202+ . include? ( node . method_name )
188203 end
189204
190- def bad_associations_in ( node )
191- children_of_factory_block ( node ) . select do |child |
192- bad? ( child )
205+ def non_implicit_association_method_names_for ( node )
206+ RuboCop ::FactoryBot . reserved_methods +
207+ configured_non_implicit_association_method_names +
208+ search_defined_trait_names_in_same_factory ( node )
209+ end
210+
211+ def configured_non_implicit_association_method_names
212+ ( cop_config [ 'NonImplicitAssociationMethodNames' ] || [ ] ) . map ( &:to_sym )
213+ end
214+
215+ def search_defined_trait_names_in_same_factory ( node )
216+ factory_definition_node = find_factory_definition_node_from ( node )
217+ return [ ] unless factory_definition_node
218+
219+ search_defined_trait_names_in ( factory_definition_node ) . to_a
220+ end
221+
222+ def find_factory_definition_node_from ( node )
223+ node . ancestors . reverse . find do |ancestor |
224+ factory_definition_node? ( ancestor )
193225 end
194226 end
195227
196- def children_of_factory_block ( node )
228+ def keyword_explicit_association_name? ( node )
229+ KEYWORDS . include? ( node . first_argument . value )
230+ end
231+
232+ def body_nodes_from ( node )
197233 block = node . block_node
198234 return [ ] unless block
199235 return [ ] unless block . body
@@ -219,9 +255,69 @@ def non_implicit_association_method_name?(method_name)
219255 non_implicit_association_method_names . include? ( method_name . to_s )
220256 end
221257
222- def non_implicit_association_method_names
223- RuboCop ::FactoryBot . reserved_methods . map ( &:to_s ) +
224- ( cop_config [ 'NonImplicitAssociationMethodNames' ] || [ ] )
258+ def autocorrect ( corrector , node )
259+ case style
260+ when :explicit
261+ autocorrect_to_explicit_style ( corrector , node )
262+ when :implicit
263+ autocorrect_to_implicit_style ( corrector , node )
264+ end
265+ end
266+
267+ def autocorrect_to_explicit_style ( corrector , node )
268+ if implicit_association? ( node )
269+ autocorrect_from_implicit_to_explicit_style ( corrector , node )
270+ else
271+ autocorrect_from_inline_to_explicit_style ( corrector , node )
272+ end
273+ end
274+
275+ def autocorrect_to_implicit_style ( corrector , node )
276+ if explicit_association? ( node )
277+ autocorrect_from_explicit_to_implicit_style ( corrector , node )
278+ else
279+ autocorrect_from_inline_to_implicit_style ( corrector , node )
280+ end
281+ end
282+
283+ def autocorrect_from_explicit_to_implicit_style ( corrector , node )
284+ source = node . first_argument . value . to_s
285+ options = options_for_autocorrect_to_implicit_style ( node )
286+ unless options . empty?
287+ rest = options . map { |option | option . join ( ': ' ) } . join ( ', ' )
288+ source += " #{ rest } "
289+ end
290+ corrector . replace ( node , source )
291+ end
292+
293+ def autocorrect_from_implicit_to_explicit_style ( corrector , node )
294+ arguments = [
295+ ":#{ node . method_name } " ,
296+ *node . arguments . map ( &:source )
297+ ]
298+ corrector . replace ( node , "association #{ arguments . join ( ', ' ) } " )
299+ end
300+
301+ def autocorrect_from_inline_to_explicit_style ( corrector , node )
302+ return unless autocorrectable_inline_association? ( node )
303+
304+ corrector . replace (
305+ node ,
306+ format (
307+ 'association %<association_name>s, factory: [%<factory_name>s]' ,
308+ association_name : node . method_name . inspect ,
309+ factory_name : node . body . first_argument . source
310+ )
311+ )
312+ end
313+
314+ # Autocorrect to implicit style with the next trial.
315+ alias autocorrect_from_inline_to_implicit_style \
316+ autocorrect_from_inline_to_explicit_style
317+
318+ # Parsing of options is too complex, so it is not currently supported.
319+ def autocorrectable_inline_association? ( node )
320+ node . body . arguments . size == 1
225321 end
226322
227323 def options_from_explicit ( node )
@@ -240,16 +336,6 @@ def options_for_autocorrect_to_implicit_style(node)
240336 end
241337 options
242338 end
243-
244- def trait_within_trait? ( node , factory_node )
245- trait_name ( factory_node ) . include? ( node . method_name )
246- end
247-
248- def trait_factory_node ( node )
249- node . ancestors . reverse . find do |ancestor |
250- ancestor . method? ( :factory ) if ancestor . block_type?
251- end
252- end
253339 end
254340 end
255341 end
0 commit comments