rubocop-rspec 2.22.0 → 2.27.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 +4 -4
- data/CHANGELOG.md +94 -9
- data/README.md +1 -1
- data/config/default.yml +126 -21
- data/lib/rubocop/cop/rspec/around_block.rb +3 -3
- data/lib/rubocop/cop/rspec/be.rb +1 -1
- data/lib/rubocop/cop/rspec/be_empty.rb +1 -0
- data/lib/rubocop/cop/rspec/be_eq.rb +1 -1
- data/lib/rubocop/cop/rspec/be_eql.rb +1 -1
- data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
- data/lib/rubocop/cop/rspec/before_after_all.rb +7 -13
- data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +2 -2
- data/lib/rubocop/cop/rspec/change_by_zero.rb +30 -4
- data/lib/rubocop/cop/rspec/context_method.rb +2 -2
- data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
- data/lib/rubocop/cop/rspec/describe_symbol.rb +1 -1
- data/lib/rubocop/cop/rspec/described_class.rb +33 -11
- data/lib/rubocop/cop/rspec/duplicated_metadata.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_example_group.rb +4 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +2 -2
- data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
- data/lib/rubocop/cop/rspec/eq.rb +47 -0
- data/lib/rubocop/cop/rspec/example_length.rb +11 -5
- data/lib/rubocop/cop/rspec/example_without_description.rb +11 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +11 -2
- data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
- data/lib/rubocop/cop/rspec/expect_actual.rb +7 -4
- data/lib/rubocop/cop/rspec/expect_change.rb +2 -2
- data/lib/rubocop/cop/rspec/expect_output.rb +1 -4
- data/lib/rubocop/cop/rspec/file_path.rb +6 -0
- data/lib/rubocop/cop/rspec/focus.rb +17 -2
- data/lib/rubocop/cop/rspec/hook_argument.rb +2 -2
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -1
- data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +2 -2
- data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -1
- data/lib/rubocop/cop/rspec/implicit_subject.rb +2 -2
- data/lib/rubocop/cop/rspec/indexed_let.rb +32 -1
- data/lib/rubocop/cop/rspec/instance_spy.rb +2 -2
- data/lib/rubocop/cop/rspec/instance_variable.rb +3 -3
- data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -3
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +5 -1
- data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/message_expectation.rb +1 -2
- data/lib/rubocop/cop/rspec/message_spies.rb +0 -2
- data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
- data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +2 -2
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +12 -7
- data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/pending.rb +12 -2
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +9 -9
- data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/rails/have_http_status.rb +34 -10
- data/lib/rubocop/cop/rspec/rails/http_status.rb +29 -18
- data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +314 -22
- data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
- data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
- data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
- data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
- data/lib/rubocop/cop/rspec/remove_const.rb +40 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +2 -2
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_subject_call.rb +124 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
- data/lib/rubocop/cop/rspec/shared_examples.rb +66 -20
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +2 -3
- data/lib/rubocop/cop/rspec/sort_metadata.rb +2 -1
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
- data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
- data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
- data/lib/rubocop/cop/rspec/subject_stub.rb +4 -4
- data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -2
- data/lib/rubocop/cop/rspec/variable_definition.rb +4 -4
- data/lib/rubocop/cop/rspec/verified_double_reference.rb +6 -6
- data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -2
- data/lib/rubocop/cop/rspec/void_expect.rb +4 -3
- data/lib/rubocop/cop/rspec_cops.rb +11 -0
- data/lib/rubocop/rspec/language.rb +8 -8
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop/rspec/wording.rb +8 -0
- data/lib/rubocop-rspec.rb +1 -0
- metadata +20 -8
@@ -4,54 +4,346 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module RSpec
|
6
6
|
module Rails
|
7
|
-
# Check if using Minitest matchers.
|
7
|
+
# Check if using Minitest-like matchers.
|
8
|
+
#
|
9
|
+
# Check the use of minitest-like matchers
|
10
|
+
# starting with `assert_` or `refute_`.
|
8
11
|
#
|
9
12
|
# @example
|
10
13
|
# # bad
|
11
14
|
# assert_equal(a, b)
|
12
15
|
# assert_equal a, b, "must be equal"
|
16
|
+
# assert_not_includes a, b
|
13
17
|
# refute_equal(a, b)
|
18
|
+
# assert_nil a
|
19
|
+
# refute_empty(b)
|
20
|
+
# assert_true(a)
|
21
|
+
# assert_false(a)
|
14
22
|
#
|
15
23
|
# # good
|
16
24
|
# expect(b).to eq(a)
|
17
25
|
# expect(b).to(eq(a), "must be equal")
|
26
|
+
# expect(a).not_to include(b)
|
18
27
|
# expect(b).not_to eq(a)
|
28
|
+
# expect(a).to eq(nil)
|
29
|
+
# expect(a).not_to be_empty
|
30
|
+
# expect(a).to be(true)
|
31
|
+
# expect(a).to be(false)
|
19
32
|
#
|
20
33
|
class MinitestAssertions < Base
|
21
34
|
extend AutoCorrector
|
22
35
|
|
36
|
+
# :nodoc:
|
37
|
+
class BasicAssertion
|
38
|
+
extend NodePattern::Macros
|
39
|
+
|
40
|
+
attr_reader :expected, :actual, :failure_message
|
41
|
+
|
42
|
+
def self.minitest_assertion
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(expected, actual, failure_message)
|
47
|
+
@expected = expected&.source
|
48
|
+
@actual = actual.source
|
49
|
+
@failure_message = failure_message&.source
|
50
|
+
end
|
51
|
+
|
52
|
+
def replaced(node)
|
53
|
+
runner = negated?(node) ? 'not_to' : 'to'
|
54
|
+
if failure_message.nil?
|
55
|
+
"expect(#{actual}).#{runner} #{assertion}"
|
56
|
+
else
|
57
|
+
"expect(#{actual}).#{runner}(#{assertion}, #{failure_message})"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def negated?(node)
|
62
|
+
node.method_name.start_with?('assert_not_', 'refute_')
|
63
|
+
end
|
64
|
+
|
65
|
+
def assertion
|
66
|
+
raise NotImplementedError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# :nodoc:
|
71
|
+
class EqualAssertion < BasicAssertion
|
72
|
+
MATCHERS = %i[
|
73
|
+
assert_equal
|
74
|
+
assert_not_equal
|
75
|
+
refute_equal
|
76
|
+
].freeze
|
77
|
+
|
78
|
+
# @!method self.minitest_assertion(node)
|
79
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
80
|
+
(send nil? {:assert_equal :assert_not_equal :refute_equal} $_ $_ $_?)
|
81
|
+
PATTERN
|
82
|
+
|
83
|
+
def self.match(expected, actual, failure_message)
|
84
|
+
new(expected, actual, failure_message.first)
|
85
|
+
end
|
86
|
+
|
87
|
+
def assertion
|
88
|
+
"eq(#{expected})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# :nodoc:
|
93
|
+
class KindOfAssertion < BasicAssertion
|
94
|
+
MATCHERS = %i[
|
95
|
+
assert_kind_of
|
96
|
+
assert_not_kind_of
|
97
|
+
refute_kind_of
|
98
|
+
].freeze
|
99
|
+
|
100
|
+
# @!method self.minitest_assertion(node)
|
101
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
102
|
+
(send nil? {:assert_kind_of :assert_not_kind_of :refute_kind_of} $_ $_ $_?)
|
103
|
+
PATTERN
|
104
|
+
|
105
|
+
def self.match(expected, actual, failure_message)
|
106
|
+
new(expected, actual, failure_message.first)
|
107
|
+
end
|
108
|
+
|
109
|
+
def assertion
|
110
|
+
"be_a_kind_of(#{expected})"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# :nodoc:
|
115
|
+
class InstanceOfAssertion < BasicAssertion
|
116
|
+
MATCHERS = %i[
|
117
|
+
assert_instance_of
|
118
|
+
assert_not_instance_of
|
119
|
+
refute_instance_of
|
120
|
+
].freeze
|
121
|
+
|
122
|
+
# @!method self.minitest_assertion(node)
|
123
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
124
|
+
(send nil? {:assert_instance_of :assert_not_instance_of :refute_instance_of} $_ $_ $_?)
|
125
|
+
PATTERN
|
126
|
+
|
127
|
+
def self.match(expected, actual, failure_message)
|
128
|
+
new(expected, actual, failure_message.first)
|
129
|
+
end
|
130
|
+
|
131
|
+
def assertion
|
132
|
+
"be_an_instance_of(#{expected})"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# :nodoc:
|
137
|
+
class IncludesAssertion < BasicAssertion
|
138
|
+
MATCHERS = %i[
|
139
|
+
assert_includes
|
140
|
+
assert_not_includes
|
141
|
+
refute_includes
|
142
|
+
].freeze
|
143
|
+
|
144
|
+
# @!method self.minitest_assertion(node)
|
145
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
146
|
+
(send nil? {:assert_includes :assert_not_includes :refute_includes} $_ $_ $_?)
|
147
|
+
PATTERN
|
148
|
+
|
149
|
+
def self.match(collection, expected, failure_message)
|
150
|
+
new(expected, collection, failure_message.first)
|
151
|
+
end
|
152
|
+
|
153
|
+
def assertion
|
154
|
+
"include(#{expected})"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# :nodoc:
|
159
|
+
class InDeltaAssertion < BasicAssertion
|
160
|
+
MATCHERS = %i[
|
161
|
+
assert_in_delta
|
162
|
+
assert_not_in_delta
|
163
|
+
refute_in_delta
|
164
|
+
].freeze
|
165
|
+
|
166
|
+
# @!method self.minitest_assertion(node)
|
167
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
168
|
+
(send nil? {:assert_in_delta :assert_not_in_delta :refute_in_delta} $_ $_ $_? $_?)
|
169
|
+
PATTERN
|
170
|
+
|
171
|
+
def self.match(expected, actual, delta, failure_message)
|
172
|
+
new(expected, actual, delta.first, failure_message.first)
|
173
|
+
end
|
174
|
+
|
175
|
+
def initialize(expected, actual, delta, fail_message)
|
176
|
+
super(expected, actual, fail_message)
|
177
|
+
|
178
|
+
@delta = delta&.source || '0.001'
|
179
|
+
end
|
180
|
+
|
181
|
+
def assertion
|
182
|
+
"be_within(#{@delta}).of(#{expected})"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# :nodoc:
|
187
|
+
class PredicateAssertion < BasicAssertion
|
188
|
+
MATCHERS = %i[
|
189
|
+
assert_predicate
|
190
|
+
assert_not_predicate
|
191
|
+
refute_predicate
|
192
|
+
].freeze
|
193
|
+
|
194
|
+
# @!method self.minitest_assertion(node)
|
195
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
196
|
+
(send nil? {:assert_predicate :assert_not_predicate :refute_predicate} $_ ${sym} $_?)
|
197
|
+
PATTERN
|
198
|
+
|
199
|
+
def self.match(subject, predicate, failure_message)
|
200
|
+
return nil unless predicate.value.end_with?('?')
|
201
|
+
|
202
|
+
new(predicate, subject, failure_message.first)
|
203
|
+
end
|
204
|
+
|
205
|
+
def assertion
|
206
|
+
"be_#{expected.delete_prefix(':').delete_suffix('?')}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# :nodoc:
|
211
|
+
class MatchAssertion < BasicAssertion
|
212
|
+
MATCHERS = %i[
|
213
|
+
assert_match
|
214
|
+
refute_match
|
215
|
+
].freeze
|
216
|
+
|
217
|
+
# @!method self.minitest_assertion(node)
|
218
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
219
|
+
(send nil? {:assert_match :refute_match} $_ $_ $_?)
|
220
|
+
PATTERN
|
221
|
+
|
222
|
+
def self.match(matcher, actual, failure_message)
|
223
|
+
new(matcher, actual, failure_message.first)
|
224
|
+
end
|
225
|
+
|
226
|
+
def assertion
|
227
|
+
"match(#{expected})"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# :nodoc:
|
232
|
+
class NilAssertion < BasicAssertion
|
233
|
+
MATCHERS = %i[
|
234
|
+
assert_nil
|
235
|
+
assert_not_nil
|
236
|
+
refute_nil
|
237
|
+
].freeze
|
238
|
+
|
239
|
+
# @!method self.minitest_assertion(node)
|
240
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
241
|
+
(send nil? {:assert_nil :assert_not_nil :refute_nil} $_ $_?)
|
242
|
+
PATTERN
|
243
|
+
|
244
|
+
def self.match(actual, failure_message)
|
245
|
+
new(nil, actual, failure_message.first)
|
246
|
+
end
|
247
|
+
|
248
|
+
def assertion
|
249
|
+
'eq(nil)'
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# :nodoc:
|
254
|
+
class EmptyAssertion < BasicAssertion
|
255
|
+
MATCHERS = %i[
|
256
|
+
assert_empty
|
257
|
+
assert_not_empty
|
258
|
+
refute_empty
|
259
|
+
].freeze
|
260
|
+
|
261
|
+
# @!method self.minitest_assertion(node)
|
262
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
263
|
+
(send nil? {:assert_empty :assert_not_empty :refute_empty} $_ $_?)
|
264
|
+
PATTERN
|
265
|
+
|
266
|
+
def self.match(actual, failure_message)
|
267
|
+
new(nil, actual, failure_message.first)
|
268
|
+
end
|
269
|
+
|
270
|
+
def assertion
|
271
|
+
'be_empty'
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# :nodoc:
|
276
|
+
class TrueAssertion < BasicAssertion
|
277
|
+
MATCHERS = %i[
|
278
|
+
assert_true
|
279
|
+
].freeze
|
280
|
+
|
281
|
+
# @!method self.minitest_assertion(node)
|
282
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
283
|
+
(send nil? {:assert_true} $_ $_?)
|
284
|
+
PATTERN
|
285
|
+
|
286
|
+
def self.match(actual, failure_message)
|
287
|
+
new(nil, actual, failure_message.first)
|
288
|
+
end
|
289
|
+
|
290
|
+
def assertion
|
291
|
+
'be(true)'
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# :nodoc:
|
296
|
+
class FalseAssertion < BasicAssertion
|
297
|
+
MATCHERS = %i[
|
298
|
+
assert_false
|
299
|
+
].freeze
|
300
|
+
|
301
|
+
# @!method self.minitest_assertion(node)
|
302
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
303
|
+
(send nil? {:assert_false} $_ $_?)
|
304
|
+
PATTERN
|
305
|
+
|
306
|
+
def self.match(actual, failure_message)
|
307
|
+
new(nil, actual, failure_message.first)
|
308
|
+
end
|
309
|
+
|
310
|
+
def assertion
|
311
|
+
'be(false)'
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
23
315
|
MSG = 'Use `%<prefer>s`.'
|
24
|
-
RESTRICT_ON_SEND = %i[assert_equal refute_equal].freeze
|
25
316
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
317
|
+
# TODO: replace with `BasicAssertion.subclasses` in Ruby 3.1+
|
318
|
+
ASSERTION_MATCHERS = constants(false).filter_map do |c|
|
319
|
+
const = const_get(c)
|
320
|
+
|
321
|
+
const if const.is_a?(Class) && const.superclass == BasicAssertion
|
322
|
+
end
|
323
|
+
|
324
|
+
RESTRICT_ON_SEND = ASSERTION_MATCHERS.flat_map { |m| m::MATCHERS }
|
30
325
|
|
31
326
|
def on_send(node)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
327
|
+
ASSERTION_MATCHERS.each do |m|
|
328
|
+
m.minitest_assertion(node) do |*args|
|
329
|
+
assertion = m.match(*args)
|
330
|
+
|
331
|
+
next if assertion.nil?
|
332
|
+
|
333
|
+
on_assertion(node, assertion)
|
37
334
|
end
|
38
335
|
end
|
39
336
|
end
|
40
337
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
if failure_message.nil?
|
46
|
-
"expect(#{actual.source}).#{runner} eq(#{expected.source})"
|
47
|
-
else
|
48
|
-
"expect(#{actual.source}).#{runner}(eq(#{expected.source}), " \
|
49
|
-
"#{failure_message.source})"
|
338
|
+
def on_assertion(node, assertion)
|
339
|
+
preferred = assertion.replaced(node)
|
340
|
+
add_offense(node, message: message(preferred)) do |corrector|
|
341
|
+
corrector.replace(node, preferred)
|
50
342
|
end
|
51
343
|
end
|
52
344
|
|
53
|
-
def message(
|
54
|
-
format(MSG, prefer:
|
345
|
+
def message(preferred)
|
346
|
+
format(MSG, prefer: preferred)
|
55
347
|
end
|
56
348
|
end
|
57
349
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Rails
|
7
|
+
# Enforces use of `be_invalid` or `not_to` for negated be_valid.
|
8
|
+
#
|
9
|
+
# @safety
|
10
|
+
# This cop is unsafe because it cannot guarantee that
|
11
|
+
# the test target is an instance of `ActiveModel::Validations``.
|
12
|
+
#
|
13
|
+
# @example EnforcedStyle: not_to (default)
|
14
|
+
# # bad
|
15
|
+
# expect(foo).to be_invalid
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# expect(foo).not_to be_valid
|
19
|
+
#
|
20
|
+
# # good (with method chain)
|
21
|
+
# expect(foo).to be_invalid.and be_odd
|
22
|
+
#
|
23
|
+
# @example EnforcedStyle: be_invalid
|
24
|
+
# # bad
|
25
|
+
# expect(foo).not_to be_valid
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# expect(foo).to be_invalid
|
29
|
+
#
|
30
|
+
# # good (with method chain)
|
31
|
+
# expect(foo).to be_invalid.or be_even
|
32
|
+
#
|
33
|
+
class NegationBeValid < Base
|
34
|
+
extend AutoCorrector
|
35
|
+
include ConfigurableEnforcedStyle
|
36
|
+
|
37
|
+
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
|
38
|
+
RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze
|
39
|
+
|
40
|
+
# @!method not_to?(node)
|
41
|
+
def_node_matcher :not_to?, <<~PATTERN
|
42
|
+
(send ... :not_to (send nil? :be_valid ...))
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
# @!method be_invalid?(node)
|
46
|
+
def_node_matcher :be_invalid?, <<~PATTERN
|
47
|
+
(send ... :to (send nil? :be_invalid ...))
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
def on_send(node)
|
51
|
+
return unless offense?(node.parent)
|
52
|
+
|
53
|
+
add_offense(offense_range(node),
|
54
|
+
message: message(node.method_name)) do |corrector|
|
55
|
+
corrector.replace(node.parent.loc.selector, replaced_runner)
|
56
|
+
corrector.replace(node.loc.selector, replaced_matcher)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def offense?(node)
|
63
|
+
case style
|
64
|
+
when :not_to
|
65
|
+
be_invalid?(node)
|
66
|
+
when :be_invalid
|
67
|
+
not_to?(node)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def offense_range(node)
|
72
|
+
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
|
73
|
+
end
|
74
|
+
|
75
|
+
def message(_matcher)
|
76
|
+
format(MSG,
|
77
|
+
runner: replaced_runner,
|
78
|
+
matcher: replaced_matcher)
|
79
|
+
end
|
80
|
+
|
81
|
+
def replaced_runner
|
82
|
+
case style
|
83
|
+
when :not_to
|
84
|
+
'not_to'
|
85
|
+
when :be_invalid
|
86
|
+
'to'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def replaced_matcher
|
91
|
+
case style
|
92
|
+
when :not_to
|
93
|
+
'be_valid'
|
94
|
+
when :be_invalid
|
95
|
+
'be_invalid'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -30,7 +30,7 @@ module RuboCop
|
|
30
30
|
RESTRICT_ON_SEND = %i[times].freeze
|
31
31
|
|
32
32
|
# @!method receive_counts(node)
|
33
|
-
def_node_matcher :receive_counts,
|
33
|
+
def_node_matcher :receive_counts, <<~PATTERN
|
34
34
|
(send $(send _ {:exactly :at_least :at_most} (int {1 2})) :times)
|
35
35
|
PATTERN
|
36
36
|
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for multiple messages stubbed on the same object.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# The autocorrection is marked as unsafe, because it may change the
|
10
|
+
# order of stubs. This in turn may cause e.g. variables to be called
|
11
|
+
# before they are defined.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# before do
|
16
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
17
|
+
# allow(Service).to receive(:baz).and_return(qux)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# before do
|
22
|
+
# allow(Service).to receive_messages(foo: bar, baz: qux)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # good - ignore same message
|
26
|
+
# before do
|
27
|
+
# allow(Service).to receive(:foo).and_return(bar)
|
28
|
+
# allow(Service).to receive(:foo).and_return(qux)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
class ReceiveMessages < Base
|
32
|
+
extend AutoCorrector
|
33
|
+
include RangeHelp
|
34
|
+
|
35
|
+
MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
|
36
|
+
'%<loc>s.'
|
37
|
+
|
38
|
+
# @!method allow_receive_message?(node)
|
39
|
+
def_node_matcher :allow_receive_message?, <<~PATTERN
|
40
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc_or_splat?))
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
# @!method allow_argument(node)
|
44
|
+
def_node_matcher :allow_argument, <<~PATTERN
|
45
|
+
(send (send nil? :allow $_ ...) ...)
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
# @!method receive_node(node)
|
49
|
+
def_node_search :receive_node, <<~PATTERN
|
50
|
+
$(send (send nil? :receive ...) ...)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
# @!method receive_arg(node)
|
54
|
+
def_node_search :receive_arg, <<~PATTERN
|
55
|
+
(send (send nil? :receive $_) ...)
|
56
|
+
PATTERN
|
57
|
+
|
58
|
+
# @!method receive_and_return_argument(node)
|
59
|
+
def_node_matcher :receive_and_return_argument, <<~PATTERN
|
60
|
+
(send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
|
61
|
+
PATTERN
|
62
|
+
|
63
|
+
def on_begin(node)
|
64
|
+
repeated_receive_message(node).each do |item, repeated_lines, args|
|
65
|
+
next if repeated_lines.empty?
|
66
|
+
|
67
|
+
register_offense(item, repeated_lines, args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def repeated_receive_message(node)
|
74
|
+
node
|
75
|
+
.children
|
76
|
+
.select { |child| allow_receive_message?(child) }
|
77
|
+
.group_by { |child| allow_argument(child) }
|
78
|
+
.values
|
79
|
+
.reject(&:one?)
|
80
|
+
.flat_map { |items| add_repeated_lines_and_arguments(items) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_repeated_lines_and_arguments(items)
|
84
|
+
uniq_items = uniq_items(items)
|
85
|
+
repeated_lines = uniq_items.map(&:first_line)
|
86
|
+
uniq_items.map do |item|
|
87
|
+
[item, repeated_lines - [item.first_line], arguments(uniq_items)]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def uniq_items(items)
|
92
|
+
items.select do |item|
|
93
|
+
items.none? do |i|
|
94
|
+
receive_arg(item).first == receive_arg(i).first &&
|
95
|
+
!same_line?(item, i)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def arguments(items)
|
101
|
+
items.map do |item|
|
102
|
+
receive_and_return_argument(item) do |receive_arg, return_arg|
|
103
|
+
"#{normalize_receive_arg(receive_arg)}: " \
|
104
|
+
"#{normalize_return_arg(return_arg)}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def normalize_receive_arg(receive_arg)
|
110
|
+
if requires_quotes?(receive_arg)
|
111
|
+
"'#{receive_arg}'"
|
112
|
+
else
|
113
|
+
receive_arg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def normalize_return_arg(return_arg)
|
118
|
+
if return_arg.hash_type? && !return_arg.braces?
|
119
|
+
"{ #{return_arg.source} }"
|
120
|
+
else
|
121
|
+
return_arg.source
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def register_offense(item, repeated_lines, args)
|
126
|
+
add_offense(item, message: message(repeated_lines)) do |corrector|
|
127
|
+
if item.loc.line > repeated_lines.max
|
128
|
+
replace_to_receive_messages(corrector, item, args)
|
129
|
+
else
|
130
|
+
corrector.remove(item_range_by_whole_lines(item))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def message(repeated_lines)
|
136
|
+
format(MSG, loc: repeated_lines)
|
137
|
+
end
|
138
|
+
|
139
|
+
def replace_to_receive_messages(corrector, item, args)
|
140
|
+
receive_node(item) do |node|
|
141
|
+
corrector.replace(node,
|
142
|
+
"receive_messages(#{args.join(', ')})")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def item_range_by_whole_lines(item)
|
147
|
+
range_by_whole_lines(item.source_range, include_final_newline: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
def heredoc_or_splat?(node)
|
151
|
+
((node.str_type? || node.dstr_type?) && node.heredoc?) ||
|
152
|
+
node.splat_type?
|
153
|
+
end
|
154
|
+
|
155
|
+
def requires_quotes?(value)
|
156
|
+
value.match?(/^:".*?"|=$|^\W+$/)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for redundant predicate matcher.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# expect(foo).to be_exist(bar)
|
11
|
+
# expect(foo).not_to be_include(bar)
|
12
|
+
# expect(foo).to be_all(bar)
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# expect(foo).to exist(bar)
|
16
|
+
# expect(foo).not_to include(bar)
|
17
|
+
# expect(foo).to all be(bar)
|
18
|
+
#
|
19
|
+
class RedundantPredicateMatcher < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
|
22
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
23
|
+
RESTRICT_ON_SEND =
|
24
|
+
%i[be_all be_cover be_end_with be_eql be_equal
|
25
|
+
be_exist be_exists be_include be_match
|
26
|
+
be_respond_to be_start_with].freeze
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
return if node.parent.block_type? || node.arguments.empty?
|
30
|
+
return unless replaceable_arguments?(node)
|
31
|
+
|
32
|
+
method_name = node.method_name.to_s
|
33
|
+
replaced = replaced_method_name(method_name)
|
34
|
+
add_offense(node, message: message(method_name,
|
35
|
+
replaced)) do |corrector|
|
36
|
+
unless node.method?(:be_all)
|
37
|
+
corrector.replace(node.loc.selector, replaced)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def message(bad_method, good_method)
|
45
|
+
format(MSG, bad: bad_method, good: good_method)
|
46
|
+
end
|
47
|
+
|
48
|
+
def replaceable_arguments?(node)
|
49
|
+
if node.method?(:be_all)
|
50
|
+
node.first_argument.send_type?
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def replaced_method_name(method_name)
|
57
|
+
name = method_name.to_s.delete_prefix('be_')
|
58
|
+
if name == 'exists'
|
59
|
+
'exist'
|
60
|
+
else
|
61
|
+
name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|