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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44482485fe839223a23464d6739cbd0160ebaffa5402c9057d2fdc0e3c9b3295
4
- data.tar.gz: 035ac59bb998b1268bf0803bce35b9c2ab75ac6f232a427e28996f0ea857f386
3
+ metadata.gz: 5d8a45a48d47318e9e16e7b3bbde99ae9dcf94f6e90c068f0916ff2945511e85
4
+ data.tar.gz: 52ff0ffe8fb20bc0ed3ec4dca29aa13a29b106e39f7bacac370388f0c789bf1c
5
5
  SHA512:
6
- metadata.gz: fd92b4d37f8a352e1acc98ae2bbbad05982248d3ef97dfb6b99edfe5173ba087a4259db3718e1e05c0b7951574652b657b694d2f02a5c34cf162d17fe427303a
7
- data.tar.gz: 10cf9b7446c6fbd09a2ffcfe8629be12c2dc334132d6515acd8015b0f22a7e06e01d704f42316730e096901889f189adcaff8730571df86e401e7767688c07f3
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
  [![Gem Version](https://badge.fury.io/rb/rubocop-factory_bot.svg)](https://rubygems.org/gems/rubocop-factory_bot)
5
5
  ![CI](https://github.com/rubocop/rubocop-factory_bot/workflows/CI/badge.svg)
6
6
 
7
- [Factory Bot](https://www.rubydoc.info/gems/factory_bot)-specific analysis for your projects, as an extension to
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 Factory Bot extension. There are three
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 Factory Bot cops just like any other
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 factory bot calls.
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.0'
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, <<-PATTERN
34
- (send _ !#reserved_method? $...)
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, <<-PATTERN
39
- (block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } )
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 factory bot calls.
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(:user)
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 :user
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 RuboCop::FactoryBot::Language
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, <<-PATTERN
61
- (send
62
- {#factory_bot? nil?} %FACTORY_CALLS
63
- {sym str send lvar} _*
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) do
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 process_with_parentheses(node)
84
- return unless style == :omit_parentheses
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 process_without_parentheses(node)
94
- return unless style == :require_parentheses
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 array_new_or_n_times_block?(node)
43
- def_node_matcher :array_new_or_n_times_block?, <<-PATTERN
44
- (block
45
- {
46
- (send (const {nil? | cbase} :Array) :new (int _)) |
47
- (send (int _) :times)
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?, <<-PATTERN
55
- (block
56
- _
57
- (args (arg _value))
58
- `_value
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?, <<-PATTERN
64
- (send ${nil? #factory_bot?} :create (sym $_) `$(send ...))
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, <<-PATTERN
69
- (send ${nil? #factory_bot?} :create (sym $_) $...)
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, <<-PATTERN
74
- (send {nil? #factory_bot?} :create_list (sym _) (int $_) ...)
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 array_new_or_n_times_block?(node)
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
- if node.receiver.int_type?
228
- node.receiver
229
- else
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, <<-PATTERN
59
+ def_node_matcher :factory_call, <<~PATTERN
37
60
  (send
38
- {#factory_bot? nil?} %FACTORY_CALLS
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'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- # RuboCop FactoryBot project namespace
4
+ # RuboCop factory_bot project namespace
5
5
  module FactoryBot
6
6
  ATTRIBUTE_DEFINING_METHODS = %i[
7
7
  factory
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RuboCop
4
4
  module FactoryBot
5
- # Contains node matchers for common FactoryBot DSL.
5
+ # Contains node matchers for common factory_bot DSL.
6
6
  module Language
7
7
  extend RuboCop::NodePattern::Macros
8
8
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  module RuboCop
4
4
  module FactoryBot
5
- # Version information for the FactoryBot RuboCop plugin.
5
+ # Version information for the factory_bot RuboCop plugin.
6
6
  module Version
7
- STRING = '2.22.0'
7
+ STRING = '2.23.1'
8
8
  end
9
9
  end
10
10
  end
@@ -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.22.0
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-05 00:00:00.000000000 Z
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: |2
32
- Code style checking for FactoryBot files.
33
- A plugin for the RuboCop code style enforcing & linting tool.
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.3.3
85
+ rubygems_version: 3.4.13
82
86
  signing_key:
83
87
  specification_version: 4
84
- summary: Code style checking for FactoryBot files
88
+ summary: Code style checking for factory_bot files
85
89
  test_files: []