rubocop-rspec 2.21.0 → 2.23.0

Sign up to get free protection for your applications and to get access to all the features.
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)