rubocop-rspec 2.21.0 → 2.23.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +1 -1
  4. data/config/default.yml +37 -16
  5. data/config/obsoletion.yml +6 -0
  6. data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -0
  7. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +11 -4
  8. data/lib/rubocop/cop/rspec/expect_actual.rb +2 -2
  9. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +25 -118
  10. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +40 -107
  11. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +30 -250
  12. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +19 -46
  13. data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +23 -64
  14. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +45 -79
  15. data/lib/rubocop/cop/rspec/focus.rb +13 -0
  16. data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
  17. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  18. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
  19. data/lib/rubocop/cop/rspec/let_before_examples.rb +4 -0
  20. data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
  21. data/lib/rubocop/cop/rspec/pending.rb +12 -2
  22. data/lib/rubocop/cop/rspec/predicate_matcher.rb +3 -3
  23. data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +92 -0
  24. data/lib/rubocop/cop/rspec/receive_messages.rb +155 -0
  25. data/lib/rubocop/cop/rspec/verified_double_reference.rb +1 -1
  26. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  27. data/lib/rubocop/cop/rspec_cops.rb +2 -0
  28. data/lib/rubocop/rspec/config_formatter.rb +6 -0
  29. data/lib/rubocop/rspec/version.rb +1 -1
  30. data/lib/rubocop-rspec.rb +1 -3
  31. metadata +18 -4
  32. data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
  33. data/lib/rubocop/rspec/factory_bot.rb +0 -64
@@ -4,256 +4,36 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Checks for create_list usage.
8
- #
9
- # This cop can be configured using the `EnforcedStyle` option
10
- #
11
- # @example `EnforcedStyle: create_list` (default)
12
- # # bad
13
- # 3.times { create :user }
14
- #
15
- # # good
16
- # create_list :user, 3
17
- #
18
- # # bad
19
- # 3.times { create :user, age: 18 }
20
- #
21
- # # good - index is used to alter the created models attributes
22
- # 3.times { |n| create :user, age: n }
23
- #
24
- # # good - contains a method call, may return different values
25
- # 3.times { create :user, age: rand }
26
- #
27
- # @example `EnforcedStyle: n_times`
28
- # # bad
29
- # create_list :user, 3
30
- #
31
- # # good
32
- # 3.times { create :user }
33
- #
34
- class CreateList < ::RuboCop::Cop::Base
35
- extend AutoCorrector
36
- include ConfigurableEnforcedStyle
37
- include RuboCop::RSpec::FactoryBot::Language
38
-
39
- MSG_CREATE_LIST = 'Prefer create_list.'
40
- MSG_N_TIMES = 'Prefer %<number>s.times.'
41
- RESTRICT_ON_SEND = %i[create_list].freeze
42
-
43
- # @!method array_new_or_n_times_block?(node)
44
- def_node_matcher :array_new_or_n_times_block?, <<-PATTERN
45
- (block
46
- {
47
- (send (const {nil? | cbase} :Array) :new (int _)) |
48
- (send (int _) :times)
49
- }
50
- ...
51
- )
52
- PATTERN
53
-
54
- # @!method block_with_arg_and_used?(node)
55
- def_node_matcher :block_with_arg_and_used?, <<-PATTERN
56
- (block
57
- _
58
- (args (arg _value))
59
- `_value
60
- )
61
- PATTERN
62
-
63
- # @!method arguments_include_method_call?(node)
64
- def_node_matcher :arguments_include_method_call?, <<-PATTERN
65
- (send ${nil? #factory_bot?} :create (sym $_) `$(send ...))
66
- PATTERN
67
-
68
- # @!method factory_call(node)
69
- def_node_matcher :factory_call, <<-PATTERN
70
- (send ${nil? #factory_bot?} :create (sym $_) $...)
71
- PATTERN
72
-
73
- # @!method factory_list_call(node)
74
- def_node_matcher :factory_list_call, <<-PATTERN
75
- (send {nil? #factory_bot?} :create_list (sym _) (int $_) ...)
76
- PATTERN
77
-
78
- def on_block(node) # rubocop:todo InternalAffairs/NumblockHandler
79
- return unless style == :create_list
80
-
81
- return unless array_new_or_n_times_block?(node)
82
- return if block_with_arg_and_used?(node)
83
- return unless node.body
84
- return if arguments_include_method_call?(node.body)
85
- return unless contains_only_factory?(node.body)
86
-
87
- add_offense(node.send_node, message: MSG_CREATE_LIST) do |corrector|
88
- CreateListCorrector.new(node.send_node).call(corrector)
89
- end
90
- end
91
-
92
- def on_send(node)
93
- return unless style == :n_times
94
-
95
- factory_list_call(node) do |count|
96
- message = format(MSG_N_TIMES, number: count)
97
- add_offense(node.loc.selector, message: message) do |corrector|
98
- TimesCorrector.new(node).call(corrector)
99
- end
100
- end
101
- end
102
-
103
- private
104
-
105
- def contains_only_factory?(node)
106
- if node.block_type?
107
- factory_call(node.send_node)
108
- else
109
- factory_call(node)
110
- end
111
- end
112
-
113
- # :nodoc
114
- module Corrector
115
- private
116
-
117
- def build_options_string(options)
118
- options.map(&:source).join(', ')
119
- end
120
-
121
- def format_method_call(node, method, arguments)
122
- if node.block_type? || node.parenthesized?
123
- "#{method}(#{arguments})"
124
- else
125
- "#{method} #{arguments}"
126
- end
127
- end
128
-
129
- def format_receiver(receiver)
130
- return '' unless receiver
131
-
132
- "#{receiver.source}."
133
- end
134
- end
135
-
136
- # :nodoc
137
- class TimesCorrector
138
- include Corrector
139
-
140
- def initialize(node)
141
- @node = node
142
- end
143
-
144
- def call(corrector)
145
- replacement = generate_n_times_block(node)
146
- corrector.replace(node.block_node || node, replacement)
147
- end
148
-
149
- private
150
-
151
- attr_reader :node
152
-
153
- def generate_n_times_block(node)
154
- factory, count, *options = node.arguments
155
-
156
- arguments = factory.source
157
- options = build_options_string(options)
158
- arguments += ", #{options}" unless options.empty?
159
-
160
- replacement = format_receiver(node.receiver)
161
- replacement += format_method_call(node, 'create', arguments)
162
- replacement += " #{factory_call_block_source}" if node.block_node
163
- "#{count.source}.times { #{replacement} }"
164
- end
165
-
166
- def factory_call_block_source
167
- node.block_node.location.begin.with(
168
- end_pos: node.block_node.location.end.end_pos
169
- ).source
170
- end
171
- end
172
-
173
- # :nodoc:
174
- class CreateListCorrector
175
- include Corrector
176
-
177
- def initialize(node)
178
- @node = node.parent
179
- end
180
-
181
- def call(corrector)
182
- replacement = if node.body.block_type?
183
- call_with_block_replacement(node)
184
- else
185
- call_replacement(node)
186
- end
187
-
188
- corrector.replace(node, replacement)
189
- end
190
-
191
- private
192
-
193
- attr_reader :node
194
-
195
- def call_with_block_replacement(node)
196
- block = node.body
197
- arguments = build_arguments(block, count_from(node))
198
- replacement = format_receiver(block.receiver)
199
- replacement += format_method_call(block, 'create_list', arguments)
200
- replacement += format_block(block)
201
- replacement
202
- end
203
-
204
- def build_arguments(node, count)
205
- factory, *options = *node.send_node.arguments
206
-
207
- arguments = ":#{factory.value}, #{count}"
208
- options = build_options_string(options)
209
- arguments += ", #{options}" unless options.empty?
210
- arguments
211
- end
212
-
213
- def call_replacement(node)
214
- block = node.body
215
- factory, *options = *block.arguments
216
-
217
- arguments = "#{factory.source}, #{count_from(node)}"
218
- options = build_options_string(options)
219
- arguments += ", #{options}" unless options.empty?
220
-
221
- replacement = format_receiver(block.receiver)
222
- replacement += format_method_call(block, 'create_list', arguments)
223
- replacement
224
- end
225
-
226
- def count_from(node)
227
- count_node =
228
- if node.receiver.int_type?
229
- node.receiver
230
- else
231
- node.send_node.first_argument
232
- end
233
- count_node.source
234
- end
235
-
236
- def format_block(node)
237
- if node.body.begin_type?
238
- format_multiline_block(node)
239
- else
240
- format_singleline_block(node)
241
- end
242
- end
243
-
244
- def format_multiline_block(node)
245
- indent = ' ' * node.body.loc.column
246
- indent_end = ' ' * node.parent.loc.column
247
- " do #{node.arguments.source}\n" \
248
- "#{indent}#{node.body.source}\n" \
249
- "#{indent_end}end"
250
- end
251
-
252
- def format_singleline_block(node)
253
- " { #{node.arguments.source} #{node.body.source} }"
254
- end
255
- end
256
- end
7
+ # @!parse
8
+ # # Checks for create_list usage.
9
+ # #
10
+ # # This cop can be configured using the `EnforcedStyle` option
11
+ # #
12
+ # # @example `EnforcedStyle: create_list` (default)
13
+ # # # bad
14
+ # # 3.times { create :user }
15
+ # #
16
+ # # # good
17
+ # # create_list :user, 3
18
+ # #
19
+ # # # bad
20
+ # # 3.times { create :user, age: 18 }
21
+ # #
22
+ # # # good - index is used to alter the created models attributes
23
+ # # 3.times { |n| create :user, age: n }
24
+ # #
25
+ # # # good - contains a method call, may return different values
26
+ # # 3.times { create :user, age: rand }
27
+ # #
28
+ # # @example `EnforcedStyle: n_times`
29
+ # # # bad
30
+ # # create_list :user, 3
31
+ # #
32
+ # # # good
33
+ # # 3.times { create :user }
34
+ # #
35
+ # class CreateList < ::RuboCop::Cop::Base; end
36
+ CreateList = ::RuboCop::Cop::FactoryBot::CreateList
257
37
  end
258
38
  end
259
39
  end
@@ -4,52 +4,25 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Use string value when setting the class attribute explicitly.
8
- #
9
- # This cop would promote faster tests by lazy-loading of
10
- # application files. Also, this could help you suppress potential bugs
11
- # in combination with external libraries by avoiding a preload of
12
- # application files from the factory files.
13
- #
14
- # @example
15
- # # bad
16
- # factory :foo, class: Foo do
17
- # end
18
- #
19
- # # good
20
- # factory :foo, class: 'Foo' do
21
- # end
22
- #
23
- class FactoryClassName < ::RuboCop::Cop::Base
24
- extend AutoCorrector
25
-
26
- MSG = "Pass '%<class_name>s' string instead of `%<class_name>s` " \
27
- 'constant.'
28
- ALLOWED_CONSTANTS = %w[Hash OpenStruct].freeze
29
- RESTRICT_ON_SEND = %i[factory].freeze
30
-
31
- # @!method class_name(node)
32
- def_node_matcher :class_name, <<~PATTERN
33
- (send _ :factory _ (hash <(pair (sym :class) $(const ...)) ...>))
34
- PATTERN
35
-
36
- def on_send(node)
37
- class_name(node) do |cn|
38
- next if allowed?(cn.const_name)
39
-
40
- msg = format(MSG, class_name: cn.const_name)
41
- add_offense(cn, message: msg) do |corrector|
42
- corrector.replace(cn, "'#{cn.source}'")
43
- end
44
- end
45
- end
46
-
47
- private
48
-
49
- def allowed?(const_name)
50
- ALLOWED_CONSTANTS.include?(const_name)
51
- end
52
- end
7
+ # @!parse
8
+ # # Use string value when setting the class attribute explicitly.
9
+ # #
10
+ # # This cop would promote faster tests by lazy-loading of
11
+ # # application files. Also, this could help you suppress potential
12
+ # # bugs in combination with external libraries by avoiding a preload
13
+ # # of application files from the factory files.
14
+ # #
15
+ # # @example
16
+ # # # bad
17
+ # # factory :foo, class: Foo do
18
+ # # end
19
+ # #
20
+ # # # good
21
+ # # factory :foo, class: 'Foo' do
22
+ # # end
23
+ # #
24
+ # class FactoryClassName < ::RuboCop::Cop::Base; end
25
+ FactoryClassName = ::RuboCop::Cop::FactoryBot::FactoryClassName
53
26
  end
54
27
  end
55
28
  end
@@ -4,70 +4,29 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Checks for name style for argument of FactoryBot::Syntax::Methods.
8
- #
9
- # @example EnforcedStyle: symbol (default)
10
- # # bad
11
- # create('user')
12
- # build "user", username: "NAME"
13
- #
14
- # # good
15
- # create(:user)
16
- # build :user, username: "NAME"
17
- #
18
- # @example EnforcedStyle: string
19
- # # bad
20
- # create(:user)
21
- # build :user, username: "NAME"
22
- #
23
- # # good
24
- # create('user')
25
- # build "user", username: "NAME"
26
- #
27
- class FactoryNameStyle < ::RuboCop::Cop::Base
28
- extend AutoCorrector
29
- include ConfigurableEnforcedStyle
30
- include RuboCop::RSpec::FactoryBot::Language
31
-
32
- MSG = 'Use %<prefer>s to refer to a factory.'
33
- FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS
34
- RESTRICT_ON_SEND = FACTORY_CALLS
35
-
36
- # @!method factory_call(node)
37
- def_node_matcher :factory_call, <<-PATTERN
38
- (send
39
- {#factory_bot? nil?} %FACTORY_CALLS
40
- ${str sym} ...
41
- )
42
- PATTERN
43
-
44
- def on_send(node)
45
- factory_call(node) do |name|
46
- if offense_for_symbol_style?(name)
47
- register_offense(name, name.value.to_sym.inspect)
48
- elsif offense_for_string_style?(name)
49
- register_offense(name, name.value.to_s.inspect)
50
- end
51
- end
52
- end
53
-
54
- private
55
-
56
- def offense_for_symbol_style?(name)
57
- name.str_type? && style == :symbol
58
- end
59
-
60
- def offense_for_string_style?(name)
61
- name.sym_type? && style == :string
62
- end
63
-
64
- def register_offense(name, prefer)
65
- add_offense(name,
66
- message: format(MSG, prefer: style.to_s)) do |corrector|
67
- corrector.replace(name, prefer)
68
- end
69
- end
70
- end
7
+ # @!parse
8
+ # # Checks for name style for argument of FactoryBot::Syntax::Methods.
9
+ # #
10
+ # # @example EnforcedStyle: symbol (default)
11
+ # # # bad
12
+ # # create('user')
13
+ # # build "user", username: "NAME"
14
+ # #
15
+ # # # good
16
+ # # create(:user)
17
+ # # build :user, username: "NAME"
18
+ # #
19
+ # # @example EnforcedStyle: string
20
+ # # # bad
21
+ # # create(:user)
22
+ # # build :user, username: "NAME"
23
+ # #
24
+ # # # good
25
+ # # create('user')
26
+ # # build "user", username: "NAME"
27
+ # #
28
+ # class FactoryNameStyle < ::RuboCop::Cop::Base; end
29
+ FactoryNameStyle = ::RuboCop::Cop::FactoryBot::FactoryNameStyle
71
30
  end
72
31
  end
73
32
  end
@@ -4,85 +4,51 @@ module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
6
  module FactoryBot
7
- # Use shorthands from `FactoryBot::Syntax::Methods` in your specs.
8
- #
9
- # @safety
10
- # The autocorrection is marked as unsafe because the cop
11
- # cannot verify whether you already include
12
- # `FactoryBot::Syntax::Methods` in your test suite.
13
- #
14
- # If you're using Rails, add the following configuration to
15
- # `spec/support/factory_bot.rb` and be sure to require that file in
16
- # `rails_helper.rb`:
17
- #
18
- # [source,ruby]
19
- # ----
20
- # RSpec.configure do |config|
21
- # config.include FactoryBot::Syntax::Methods
22
- # end
23
- # ----
24
- #
25
- # If you're not using Rails:
26
- #
27
- # [source,ruby]
28
- # ----
29
- # RSpec.configure do |config|
30
- # config.include FactoryBot::Syntax::Methods
31
- #
32
- # config.before(:suite) do
33
- # FactoryBot.find_definitions
34
- # end
35
- # end
36
- # ----
37
- #
38
- # @example
39
- # # bad
40
- # FactoryBot.create(:bar)
41
- # FactoryBot.build(:bar)
42
- # FactoryBot.attributes_for(:bar)
43
- #
44
- # # good
45
- # create(:bar)
46
- # build(:bar)
47
- # attributes_for(:bar)
48
- #
49
- class SyntaxMethods < Base
50
- extend AutoCorrector
51
- include InsideExampleGroup
52
- include RangeHelp
53
- include RuboCop::RSpec::FactoryBot::Language
54
-
55
- MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'
56
-
57
- RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS
58
-
59
- def on_send(node)
60
- return unless factory_bot?(node.receiver)
61
- return unless inside_example_group?(node)
62
-
63
- message = format(MSG, method: node.method_name)
64
-
65
- add_offense(crime_scene(node), message: message) do |corrector|
66
- corrector.remove(offense(node))
67
- end
68
- end
69
-
70
- private
71
-
72
- def crime_scene(node)
73
- range_between(
74
- node.source_range.begin_pos,
75
- node.loc.selector.end_pos
76
- )
77
- end
78
-
79
- def offense(node)
80
- range_between(
81
- node.source_range.begin_pos,
82
- node.loc.selector.begin_pos
83
- )
84
- end
85
- end
7
+ # @!parse
8
+ # # Use shorthands from `FactoryBot::Syntax::Methods` in your specs.
9
+ # #
10
+ # # @safety
11
+ # # The autocorrection is marked as unsafe because the cop
12
+ # # cannot verify whether you already include
13
+ # # `FactoryBot::Syntax::Methods` in your test suite.
14
+ # #
15
+ # # If you're using Rails, add the following configuration to
16
+ # # `spec/support/factory_bot.rb` and be sure to require that file
17
+ # # in `rails_helper.rb`:
18
+ # #
19
+ # # [source,ruby]
20
+ # # ----
21
+ # # RSpec.configure do |config|
22
+ # # config.include FactoryBot::Syntax::Methods
23
+ # # end
24
+ # # ----
25
+ # #
26
+ # # If you're not using Rails:
27
+ # #
28
+ # # [source,ruby]
29
+ # # ----
30
+ # # RSpec.configure do |config|
31
+ # # config.include FactoryBot::Syntax::Methods
32
+ # #
33
+ # # config.before(:suite) do
34
+ # # FactoryBot.find_definitions
35
+ # # end
36
+ # # end
37
+ # # ----
38
+ # #
39
+ # # @example
40
+ # # # bad
41
+ # # FactoryBot.create(:bar)
42
+ # # FactoryBot.build(:bar)
43
+ # # FactoryBot.attributes_for(:bar)
44
+ # #
45
+ # # # good
46
+ # # create(:bar)
47
+ # # build(:bar)
48
+ # # attributes_for(:bar)
49
+ # #
50
+ # class SyntaxMethods < ::RuboCop::Cop::Base; end
51
+ SyntaxMethods = ::RuboCop::Cop::FactoryBot::SyntaxMethods
86
52
  end
87
53
  end
88
54
  end
@@ -34,6 +34,18 @@ module RuboCop
34
34
  # # good
35
35
  # describe 'test' do; end
36
36
  #
37
+ # # bad
38
+ # shared_examples 'test', focus: true do; end
39
+ #
40
+ # # good
41
+ # shared_examples 'test' do; end
42
+ #
43
+ # # bad
44
+ # shared_context 'test', focus: true do; end
45
+ #
46
+ # # good
47
+ # shared_context 'test' do; end
48
+ #
37
49
  # # bad (does not support autocorrection)
38
50
  # focus 'test' do; end
39
51
  #
@@ -51,6 +63,7 @@ module RuboCop
51
63
  #Examples.regular
52
64
  #Examples.skipped
53
65
  #Examples.pending
66
+ #SharedGroups.all
54
67
  }
55
68
  PATTERN
56
69
 
@@ -8,6 +8,9 @@ module RuboCop
8
8
  # It makes reading the test harder because it's not clear what exactly
9
9
  # is tested by this particular example.
10
10
  #
11
+ # The configurable options `AllowedIdentifiers` and `AllowedPatterns`
12
+ # will also read those set in `Naming/VariableNumber`.
13
+ #
11
14
  # @example `Max: 1 (default)`
12
15
  # # bad
13
16
  # let(:item_1) { create(:item) }
@@ -31,7 +34,20 @@ module RuboCop
31
34
  # let(:item_1) { create(:item) }
32
35
  # let(:item_2) { create(:item) }
33
36
  #
37
+ # @example `AllowedIdentifiers: ['item_1', 'item_2']`
38
+ # # good
39
+ # let(:item_1) { create(:item) }
40
+ # let(:item_2) { create(:item) }
41
+ #
42
+ # @example `AllowedPatterns: ['item']`
43
+ # # good
44
+ # let(:item_1) { create(:item) }
45
+ # let(:item_2) { create(:item) }
46
+ #
34
47
  class IndexedLet < Base
48
+ include AllowedIdentifiers
49
+ include AllowedPattern
50
+
35
51
  MSG = 'This `let` statement uses index in its name. Please give it ' \
36
52
  'a meaningful name.'
37
53
 
@@ -69,12 +85,27 @@ module RuboCop
69
85
  end
70
86
 
71
87
  def indexed_let?(node)
72
- let?(node) && SUFFIX_INDEX_REGEX.match?(let_name(node))
88
+ let?(node) &&
89
+ SUFFIX_INDEX_REGEX.match?(let_name(node)) &&
90
+ !allowed_identifier?(let_name(node).to_s) &&
91
+ !matches_allowed_pattern?(let_name(node).to_s)
73
92
  end
74
93
 
75
94
  def let_name_stripped_index(node)
76
95
  let_name(node).to_s.gsub(INDEX_REGEX, '')
77
96
  end
97
+
98
+ def cop_config_patterns_values
99
+ Array(config.for_cop('Naming/VariableNumber')
100
+ .fetch('AllowedPatterns', [])) +
101
+ Array(cop_config.fetch('AllowedPatterns', []))
102
+ end
103
+
104
+ def allowed_identifiers
105
+ Array(config.for_cop('Naming/VariableNumber')
106
+ .fetch('AllowedIdentifiers', [])) +
107
+ Array(cop_config.fetch('AllowedIdentifiers', []))
108
+ end
78
109
  end
79
110
  end
80
111
  end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  class InstanceVariable < Base
49
49
  include TopLevelGroup
50
50
 
51
- MSG = 'Avoid instance variables use let, ' \
51
+ MSG = 'Avoid instance variables - use let, ' \
52
52
  'a method call, or a local variable (if possible).'
53
53
 
54
54
  # @!method dynamic_class?(node)