rubocop-factory_bot 2.22.0
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 +7 -0
- data/CHANGELOG.md +69 -0
- data/CODE_OF_CONDUCT.md +17 -0
- data/MIT-LICENSE.md +21 -0
- data/README.md +88 -0
- data/config/default.yml +75 -0
- data/lib/rubocop/cop/factory_bot/attribute_defined_statically.rb +126 -0
- data/lib/rubocop/cop/factory_bot/consistent_parentheses_style.rb +115 -0
- data/lib/rubocop/cop/factory_bot/create_list.rb +258 -0
- data/lib/rubocop/cop/factory_bot/factory_class_name.rb +54 -0
- data/lib/rubocop/cop/factory_bot/factory_name_style.rb +72 -0
- data/lib/rubocop/cop/factory_bot/syntax_methods.rb +119 -0
- data/lib/rubocop/cop/factory_bot_cops.rb +8 -0
- data/lib/rubocop/factory_bot/config_formatter.rb +55 -0
- data/lib/rubocop/factory_bot/description_extractor.rb +70 -0
- data/lib/rubocop/factory_bot/factory_bot.rb +62 -0
- data/lib/rubocop/factory_bot/language.rb +35 -0
- data/lib/rubocop/factory_bot/version.rb +10 -0
- data/lib/rubocop-factory_bot.rb +16 -0
- metadata +85 -0
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Checks for create_list usage.
|
7
|
+
#
|
8
|
+
# This cop can be configured using the `EnforcedStyle` option
|
9
|
+
#
|
10
|
+
# @example `EnforcedStyle: create_list` (default)
|
11
|
+
# # bad
|
12
|
+
# 3.times { create :user }
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# create_list :user, 3
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# 3.times { create :user, age: 18 }
|
19
|
+
#
|
20
|
+
# # good - index is used to alter the created models attributes
|
21
|
+
# 3.times { |n| create :user, age: n }
|
22
|
+
#
|
23
|
+
# # good - contains a method call, may return different values
|
24
|
+
# 3.times { create :user, age: rand }
|
25
|
+
#
|
26
|
+
# @example `EnforcedStyle: n_times`
|
27
|
+
# # bad
|
28
|
+
# create_list :user, 3
|
29
|
+
#
|
30
|
+
# # good
|
31
|
+
# 3.times { create :user }
|
32
|
+
#
|
33
|
+
class CreateList < ::RuboCop::Cop::Base
|
34
|
+
extend AutoCorrector
|
35
|
+
include ConfigurableEnforcedStyle
|
36
|
+
include RuboCop::FactoryBot::Language
|
37
|
+
|
38
|
+
MSG_CREATE_LIST = 'Prefer create_list.'
|
39
|
+
MSG_N_TIMES = 'Prefer %<number>s.times.'
|
40
|
+
RESTRICT_ON_SEND = %i[create_list].freeze
|
41
|
+
|
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
|
+
)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
# @!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
|
+
)
|
60
|
+
PATTERN
|
61
|
+
|
62
|
+
# @!method arguments_include_method_call?(node)
|
63
|
+
def_node_matcher :arguments_include_method_call?, <<-PATTERN
|
64
|
+
(send ${nil? #factory_bot?} :create (sym $_) `$(send ...))
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method factory_call(node)
|
68
|
+
def_node_matcher :factory_call, <<-PATTERN
|
69
|
+
(send ${nil? #factory_bot?} :create (sym $_) $...)
|
70
|
+
PATTERN
|
71
|
+
|
72
|
+
# @!method factory_list_call(node)
|
73
|
+
def_node_matcher :factory_list_call, <<-PATTERN
|
74
|
+
(send {nil? #factory_bot?} :create_list (sym _) (int $_) ...)
|
75
|
+
PATTERN
|
76
|
+
|
77
|
+
def on_block(node) # rubocop:todo InternalAffairs/NumblockHandler
|
78
|
+
return unless style == :create_list
|
79
|
+
|
80
|
+
return unless array_new_or_n_times_block?(node)
|
81
|
+
return if block_with_arg_and_used?(node)
|
82
|
+
return unless node.body
|
83
|
+
return if arguments_include_method_call?(node.body)
|
84
|
+
return unless contains_only_factory?(node.body)
|
85
|
+
|
86
|
+
add_offense(node.send_node, message: MSG_CREATE_LIST) do |corrector|
|
87
|
+
CreateListCorrector.new(node.send_node).call(corrector)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_send(node)
|
92
|
+
return unless style == :n_times
|
93
|
+
|
94
|
+
factory_list_call(node) do |count|
|
95
|
+
message = format(MSG_N_TIMES, number: count)
|
96
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
97
|
+
TimesCorrector.new(node).call(corrector)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def contains_only_factory?(node)
|
105
|
+
if node.block_type?
|
106
|
+
factory_call(node.send_node)
|
107
|
+
else
|
108
|
+
factory_call(node)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# :nodoc
|
113
|
+
module Corrector
|
114
|
+
private
|
115
|
+
|
116
|
+
def build_options_string(options)
|
117
|
+
options.map(&:source).join(', ')
|
118
|
+
end
|
119
|
+
|
120
|
+
def format_method_call(node, method, arguments)
|
121
|
+
if node.block_type? || node.parenthesized?
|
122
|
+
"#{method}(#{arguments})"
|
123
|
+
else
|
124
|
+
"#{method} #{arguments}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_receiver(receiver)
|
129
|
+
return '' unless receiver
|
130
|
+
|
131
|
+
"#{receiver.source}."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# :nodoc
|
136
|
+
class TimesCorrector
|
137
|
+
include Corrector
|
138
|
+
|
139
|
+
def initialize(node)
|
140
|
+
@node = node
|
141
|
+
end
|
142
|
+
|
143
|
+
def call(corrector)
|
144
|
+
replacement = generate_n_times_block(node)
|
145
|
+
corrector.replace(node.block_node || node, replacement)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
attr_reader :node
|
151
|
+
|
152
|
+
def generate_n_times_block(node)
|
153
|
+
factory, count, *options = node.arguments
|
154
|
+
|
155
|
+
arguments = factory.source
|
156
|
+
options = build_options_string(options)
|
157
|
+
arguments += ", #{options}" unless options.empty?
|
158
|
+
|
159
|
+
replacement = format_receiver(node.receiver)
|
160
|
+
replacement += format_method_call(node, 'create', arguments)
|
161
|
+
replacement += " #{factory_call_block_source}" if node.block_node
|
162
|
+
"#{count.source}.times { #{replacement} }"
|
163
|
+
end
|
164
|
+
|
165
|
+
def factory_call_block_source
|
166
|
+
node.block_node.location.begin.with(
|
167
|
+
end_pos: node.block_node.location.end.end_pos
|
168
|
+
).source
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# :nodoc:
|
173
|
+
class CreateListCorrector
|
174
|
+
include Corrector
|
175
|
+
|
176
|
+
def initialize(node)
|
177
|
+
@node = node.parent
|
178
|
+
end
|
179
|
+
|
180
|
+
def call(corrector)
|
181
|
+
replacement = if node.body.block_type?
|
182
|
+
call_with_block_replacement(node)
|
183
|
+
else
|
184
|
+
call_replacement(node)
|
185
|
+
end
|
186
|
+
|
187
|
+
corrector.replace(node, replacement)
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
attr_reader :node
|
193
|
+
|
194
|
+
def call_with_block_replacement(node)
|
195
|
+
block = node.body
|
196
|
+
arguments = build_arguments(block, count_from(node))
|
197
|
+
replacement = format_receiver(block.receiver)
|
198
|
+
replacement += format_method_call(block, 'create_list', arguments)
|
199
|
+
replacement += format_block(block)
|
200
|
+
replacement
|
201
|
+
end
|
202
|
+
|
203
|
+
def build_arguments(node, count)
|
204
|
+
factory, *options = *node.send_node.arguments
|
205
|
+
|
206
|
+
arguments = ":#{factory.value}, #{count}"
|
207
|
+
options = build_options_string(options)
|
208
|
+
arguments += ", #{options}" unless options.empty?
|
209
|
+
arguments
|
210
|
+
end
|
211
|
+
|
212
|
+
def call_replacement(node)
|
213
|
+
block = node.body
|
214
|
+
factory, *options = *block.arguments
|
215
|
+
|
216
|
+
arguments = "#{factory.source}, #{count_from(node)}"
|
217
|
+
options = build_options_string(options)
|
218
|
+
arguments += ", #{options}" unless options.empty?
|
219
|
+
|
220
|
+
replacement = format_receiver(block.receiver)
|
221
|
+
replacement += format_method_call(block, 'create_list', arguments)
|
222
|
+
replacement
|
223
|
+
end
|
224
|
+
|
225
|
+
def count_from(node)
|
226
|
+
count_node =
|
227
|
+
if node.receiver.int_type?
|
228
|
+
node.receiver
|
229
|
+
else
|
230
|
+
node.send_node.first_argument
|
231
|
+
end
|
232
|
+
count_node.source
|
233
|
+
end
|
234
|
+
|
235
|
+
def format_block(node)
|
236
|
+
if node.body.begin_type?
|
237
|
+
format_multiline_block(node)
|
238
|
+
else
|
239
|
+
format_singleline_block(node)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def format_multiline_block(node)
|
244
|
+
indent = ' ' * node.body.loc.column
|
245
|
+
indent_end = ' ' * node.parent.loc.column
|
246
|
+
" do #{node.arguments.source}\n" \
|
247
|
+
"#{indent}#{node.body.source}\n" \
|
248
|
+
"#{indent_end}end"
|
249
|
+
end
|
250
|
+
|
251
|
+
def format_singleline_block(node)
|
252
|
+
" { #{node.arguments.source} #{node.body.source} }"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Use string value when setting the class attribute explicitly.
|
7
|
+
#
|
8
|
+
# This cop would promote faster tests by lazy-loading of
|
9
|
+
# application files. Also, this could help you suppress potential bugs
|
10
|
+
# in combination with external libraries by avoiding a preload of
|
11
|
+
# application files from the factory files.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# factory :foo, class: Foo do
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# factory :foo, class: 'Foo' do
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class FactoryClassName < ::RuboCop::Cop::Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
25
|
+
MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
|
26
|
+
'constant.'
|
27
|
+
ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze
|
28
|
+
RESTRICT_ON_SEND = %i[factory].freeze
|
29
|
+
|
30
|
+
# @!method class_name(node)
|
31
|
+
def_node_matcher :class_name, <<~PATTERN
|
32
|
+
(send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>))
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
class_name(node) do |cn|
|
37
|
+
next if allowed?(cn.const_name)
|
38
|
+
|
39
|
+
msg = format(MSG, class_name: cn.const_name)
|
40
|
+
add_offense(cn, message: msg) do |corrector|
|
41
|
+
corrector.replace(cn, "'#{cn.source}'")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def allowed?(const_name)
|
49
|
+
ALLOWED_CONSTANTS.include?(const_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Checks for name style for argument of FactoryBot::Syntax::Methods.
|
7
|
+
#
|
8
|
+
# @example EnforcedStyle: symbol (default)
|
9
|
+
# # bad
|
10
|
+
# create('user')
|
11
|
+
# build "user", username: "NAME"
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# create(:user)
|
15
|
+
# build :user, username: "NAME"
|
16
|
+
#
|
17
|
+
# @example EnforcedStyle: string
|
18
|
+
# # bad
|
19
|
+
# create(:user)
|
20
|
+
# build :user, username: "NAME"
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# create('user')
|
24
|
+
# build "user", username: "NAME"
|
25
|
+
#
|
26
|
+
class FactoryNameStyle < ::RuboCop::Cop::Base
|
27
|
+
extend AutoCorrector
|
28
|
+
include ConfigurableEnforcedStyle
|
29
|
+
include RuboCop::FactoryBot::Language
|
30
|
+
|
31
|
+
MSG = 'Use %<prefer>s to refer to a factory.'
|
32
|
+
FACTORY_CALLS = RuboCop::FactoryBot::Language::METHODS
|
33
|
+
RESTRICT_ON_SEND = FACTORY_CALLS
|
34
|
+
|
35
|
+
# @!method factory_call(node)
|
36
|
+
def_node_matcher :factory_call, <<-PATTERN
|
37
|
+
(send
|
38
|
+
{#factory_bot? nil?} %FACTORY_CALLS
|
39
|
+
${str sym} ...
|
40
|
+
)
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
def on_send(node)
|
44
|
+
factory_call(node) do |name|
|
45
|
+
if offense_for_symbol_style?(name)
|
46
|
+
register_offense(name, name.value.to_sym.inspect)
|
47
|
+
elsif offense_for_string_style?(name)
|
48
|
+
register_offense(name, name.value.to_s.inspect)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def offense_for_symbol_style?(name)
|
56
|
+
name.str_type? && style == :symbol
|
57
|
+
end
|
58
|
+
|
59
|
+
def offense_for_string_style?(name)
|
60
|
+
name.sym_type? && style == :string
|
61
|
+
end
|
62
|
+
|
63
|
+
def register_offense(name, prefer)
|
64
|
+
add_offense(name,
|
65
|
+
message: format(MSG, prefer: style.to_s)) do |corrector|
|
66
|
+
corrector.replace(name, prefer)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module FactoryBot
|
6
|
+
# Use shorthands from `FactoryBot::Syntax::Methods` in your specs.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# The autocorrection is marked as unsafe because the cop
|
10
|
+
# cannot verify whether you already include
|
11
|
+
# `FactoryBot::Syntax::Methods` in your test suite.
|
12
|
+
#
|
13
|
+
# If you're using Rails, add the following configuration to
|
14
|
+
# `spec/support/factory_bot.rb` and be sure to require that file in
|
15
|
+
# `rails_helper.rb`:
|
16
|
+
#
|
17
|
+
# [source,ruby]
|
18
|
+
# ----
|
19
|
+
# RSpec.configure do |config|
|
20
|
+
# config.include FactoryBot::Syntax::Methods
|
21
|
+
# end
|
22
|
+
# ----
|
23
|
+
#
|
24
|
+
# If you're not using Rails:
|
25
|
+
#
|
26
|
+
# [source,ruby]
|
27
|
+
# ----
|
28
|
+
# RSpec.configure do |config|
|
29
|
+
# config.include FactoryBot::Syntax::Methods
|
30
|
+
#
|
31
|
+
# config.before(:suite) do
|
32
|
+
# FactoryBot.find_definitions
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# ----
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# # bad
|
39
|
+
# FactoryBot.create(:bar)
|
40
|
+
# FactoryBot.build(:bar)
|
41
|
+
# FactoryBot.attributes_for(:bar)
|
42
|
+
#
|
43
|
+
# # good
|
44
|
+
# create(:bar)
|
45
|
+
# build(:bar)
|
46
|
+
# attributes_for(:bar)
|
47
|
+
#
|
48
|
+
class SyntaxMethods < ::RuboCop::Cop::Base
|
49
|
+
extend AutoCorrector
|
50
|
+
include RangeHelp
|
51
|
+
include RuboCop::FactoryBot::Language
|
52
|
+
|
53
|
+
MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
|
54
|
+
|
55
|
+
RESTRICT_ON_SEND = RuboCop::FactoryBot::Language::METHODS
|
56
|
+
|
57
|
+
# @!method spec_group?(node)
|
58
|
+
def_node_matcher :spec_group?, <<~PATTERN
|
59
|
+
(block
|
60
|
+
(send
|
61
|
+
{(const {nil? cbase} :RSpec) nil?}
|
62
|
+
{
|
63
|
+
:describe :context :feature :example_group
|
64
|
+
:xdescribe :xcontext :xfeature
|
65
|
+
:fdescribe :fcontext :ffeature
|
66
|
+
:shared_examples :shared_examples_for
|
67
|
+
:shared_context
|
68
|
+
}
|
69
|
+
...)
|
70
|
+
...)
|
71
|
+
PATTERN
|
72
|
+
|
73
|
+
def on_send(node)
|
74
|
+
return unless factory_bot?(node.receiver)
|
75
|
+
|
76
|
+
return unless inside_example_group?(node)
|
77
|
+
|
78
|
+
message = format(MSG, method: node.method_name)
|
79
|
+
|
80
|
+
add_offense(crime_scene(node), message: message) do |corrector|
|
81
|
+
corrector.remove(offense(node))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def crime_scene(node)
|
88
|
+
range_between(
|
89
|
+
node.source_range.begin_pos,
|
90
|
+
node.loc.selector.end_pos
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def offense(node)
|
95
|
+
range_between(
|
96
|
+
node.source_range.begin_pos,
|
97
|
+
node.loc.selector.begin_pos
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
def inside_example_group?(node)
|
102
|
+
return spec_group?(node) if example_group_root?(node)
|
103
|
+
|
104
|
+
root = node.ancestors.find { |parent| example_group_root?(parent) }
|
105
|
+
|
106
|
+
spec_group?(root)
|
107
|
+
end
|
108
|
+
|
109
|
+
def example_group_root?(node)
|
110
|
+
node.parent.nil? || example_group_root_with_siblings?(node.parent)
|
111
|
+
end
|
112
|
+
|
113
|
+
def example_group_root_with_siblings?(node)
|
114
|
+
node.begin_type? && node.parent.nil?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'factory_bot/attribute_defined_statically'
|
4
|
+
require_relative 'factory_bot/consistent_parentheses_style'
|
5
|
+
require_relative 'factory_bot/create_list'
|
6
|
+
require_relative 'factory_bot/factory_class_name'
|
7
|
+
require_relative 'factory_bot/factory_name_style'
|
8
|
+
require_relative 'factory_bot/syntax_methods'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module FactoryBot
|
7
|
+
# Builds a YAML config file from two config hashes
|
8
|
+
class ConfigFormatter
|
9
|
+
EXTENSION_ROOT_DEPARTMENT = %r{^(FactoryBot/)}.freeze
|
10
|
+
SUBDEPARTMENTS = [].freeze
|
11
|
+
AMENDMENTS = [].freeze
|
12
|
+
COP_DOC_BASE_URL = 'https://www.rubydoc.info/gems/rubocop-factory_bot/RuboCop/Cop/'
|
13
|
+
|
14
|
+
def initialize(config, descriptions)
|
15
|
+
@config = config
|
16
|
+
@descriptions = descriptions
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump
|
20
|
+
YAML.dump(unified_config)
|
21
|
+
.gsub(EXTENSION_ROOT_DEPARTMENT, "\n\\1")
|
22
|
+
.gsub(/^(\s+)- /, '\1 - ')
|
23
|
+
.gsub('"~"', '~')
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def unified_config
|
29
|
+
cops.each_with_object(config.dup) do |cop, unified|
|
30
|
+
next if SUBDEPARTMENTS.include?(cop) || AMENDMENTS.include?(cop)
|
31
|
+
|
32
|
+
replace_nil(unified[cop])
|
33
|
+
unified[cop].merge!(descriptions.fetch(cop))
|
34
|
+
unified[cop]['Reference'] = reference(cop)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cops
|
39
|
+
(descriptions.keys | config.keys).grep(EXTENSION_ROOT_DEPARTMENT)
|
40
|
+
end
|
41
|
+
|
42
|
+
def replace_nil(config)
|
43
|
+
config.each do |key, value|
|
44
|
+
config[key] = '~' if value.nil?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def reference(cop)
|
49
|
+
COP_DOC_BASE_URL + cop
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :config, :descriptions
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module FactoryBot
|
5
|
+
# Extracts cop descriptions from YARD docstrings
|
6
|
+
class DescriptionExtractor
|
7
|
+
def initialize(yardocs)
|
8
|
+
@code_objects = yardocs.map(&CodeObject.public_method(:new))
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h
|
12
|
+
code_objects
|
13
|
+
.select(&:cop?)
|
14
|
+
.map(&:configuration)
|
15
|
+
.reduce(:merge)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :code_objects
|
21
|
+
|
22
|
+
# Decorator of a YARD code object for working with documented cops
|
23
|
+
class CodeObject
|
24
|
+
RUBOCOP_COP_CLASS_NAME = 'RuboCop::Cop::Base'
|
25
|
+
|
26
|
+
def initialize(yardoc)
|
27
|
+
@yardoc = yardoc
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test if the YARD code object documents a concrete cop class
|
31
|
+
#
|
32
|
+
# @return [Boolean]
|
33
|
+
def cop?
|
34
|
+
cop_subclass? && !abstract?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Configuration for the documented cop that would live in default.yml
|
38
|
+
#
|
39
|
+
# @return [Hash]
|
40
|
+
def configuration
|
41
|
+
{ cop_name => { 'Description' => description } }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def cop_name
|
47
|
+
Object.const_get(documented_constant).cop_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def description
|
51
|
+
yardoc.docstring.split("\n\n").first.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def documented_constant
|
55
|
+
yardoc.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
def cop_subclass?
|
59
|
+
yardoc.superclass.path == RUBOCOP_COP_CLASS_NAME
|
60
|
+
end
|
61
|
+
|
62
|
+
def abstract?
|
63
|
+
yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') }
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :yardoc
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
# RuboCop FactoryBot project namespace
|
5
|
+
module FactoryBot
|
6
|
+
ATTRIBUTE_DEFINING_METHODS = %i[
|
7
|
+
factory
|
8
|
+
ignore
|
9
|
+
trait
|
10
|
+
traits_for_enum
|
11
|
+
transient
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
UNPROXIED_METHODS = %i[
|
15
|
+
__send__
|
16
|
+
__id__
|
17
|
+
nil?
|
18
|
+
send
|
19
|
+
object_id
|
20
|
+
extend
|
21
|
+
instance_eval
|
22
|
+
initialize
|
23
|
+
block_given?
|
24
|
+
raise
|
25
|
+
caller
|
26
|
+
method
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
DEFINITION_PROXY_METHODS = %i[
|
30
|
+
add_attribute
|
31
|
+
after
|
32
|
+
association
|
33
|
+
before
|
34
|
+
callback
|
35
|
+
ignore
|
36
|
+
initialize_with
|
37
|
+
sequence
|
38
|
+
skip_create
|
39
|
+
to_create
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
RESERVED_METHODS =
|
43
|
+
DEFINITION_PROXY_METHODS +
|
44
|
+
UNPROXIED_METHODS +
|
45
|
+
ATTRIBUTE_DEFINING_METHODS
|
46
|
+
|
47
|
+
private_constant(
|
48
|
+
:ATTRIBUTE_DEFINING_METHODS,
|
49
|
+
:UNPROXIED_METHODS,
|
50
|
+
:DEFINITION_PROXY_METHODS,
|
51
|
+
:RESERVED_METHODS
|
52
|
+
)
|
53
|
+
|
54
|
+
def self.attribute_defining_methods
|
55
|
+
ATTRIBUTE_DEFINING_METHODS
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.reserved_methods
|
59
|
+
RESERVED_METHODS
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|