rubocop-rspec 2.25.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 +35 -0
- data/config/default.yml +39 -4
- 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/empty_example_group.rb +2 -2
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +2 -2
- 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 +1 -1
- data/lib/rubocop/cop/rspec/expect_actual.rb +5 -2
- 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/focus.rb +2 -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/instance_spy.rb +2 -2
- data/lib/rubocop/cop/rspec/instance_variable.rb +2 -2
- 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/let_before_examples.rb +1 -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/mixin/skip_or_pending.rb +2 -2
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +12 -7
- data/lib/rubocop/cop/rspec/pending.rb +1 -1
- data/lib/rubocop/cop/rspec/pending_without_reason.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +6 -6
- 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 +1 -1
- data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +314 -22
- data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
- data/lib/rubocop/cop/rspec/receive_messages.rb +1 -1
- 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/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 +2 -2
- data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
- data/lib/rubocop/cop/rspec/void_expect.rb +2 -2
- data/lib/rubocop/cop/rspec_cops.rb +4 -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
- metadata +10 -6
@@ -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
|
@@ -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,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
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks that `remove_const` is not used in specs.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# it 'does something' do
|
11
|
+
# Object.send(:remove_const, :SomeConstant)
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# before do
|
15
|
+
# SomeClass.send(:remove_const, :SomeConstant)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class RemoveConst < Base
|
19
|
+
include RuboCop::RSpec::Language
|
20
|
+
extend RuboCop::RSpec::Language::NodePattern
|
21
|
+
|
22
|
+
MSG = 'Do not use remove_const in specs. ' \
|
23
|
+
'Consider using e.g. `stub_const`.'
|
24
|
+
RESTRICT_ON_SEND = %i[send __send__].freeze
|
25
|
+
|
26
|
+
# @!method remove_const(node)
|
27
|
+
def_node_matcher :remove_const, <<~PATTERN
|
28
|
+
(send _ {:send | :__send__} (sym :remove_const) _)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
# Check for offenses
|
32
|
+
def on_send(node)
|
33
|
+
remove_const(node) do
|
34
|
+
add_offense(node)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
|
49
49
|
|
50
50
|
# @!method several_example_groups?(node)
|
51
|
-
def_node_matcher :several_example_groups?,
|
51
|
+
def_node_matcher :several_example_groups?, <<~PATTERN
|
52
52
|
(begin <#example_group_with_body? #example_group_with_body? ...>)
|
53
53
|
PATTERN
|
54
54
|
|
@@ -48,12 +48,12 @@ module RuboCop
|
|
48
48
|
MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
|
49
49
|
|
50
50
|
# @!method several_example_groups?(node)
|
51
|
-
def_node_matcher :several_example_groups?,
|
51
|
+
def_node_matcher :several_example_groups?, <<~PATTERN
|
52
52
|
(begin <#example_group? #example_group? ...>)
|
53
53
|
PATTERN
|
54
54
|
|
55
55
|
# @!method doc_string_and_metadata(node)
|
56
|
-
def_node_matcher :doc_string_and_metadata,
|
56
|
+
def_node_matcher :doc_string_and_metadata, <<~PATTERN
|
57
57
|
(block (send _ _ $_ $...) ...)
|
58
58
|
PATTERN
|
59
59
|
|
@@ -50,7 +50,7 @@ module RuboCop
|
|
50
50
|
'on line(s) %<repeat>s'
|
51
51
|
|
52
52
|
# @!method several_include_examples?(node)
|
53
|
-
def_node_matcher :several_include_examples?,
|
53
|
+
def_node_matcher :several_include_examples?, <<~PATTERN
|
54
54
|
(begin <#include_examples? #include_examples? ...>)
|
55
55
|
PATTERN
|
56
56
|
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Checks for repeated calls to subject missing that it is memoized.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# it do
|
11
|
+
# subject
|
12
|
+
# expect { subject }.to not_change { A.count }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# it do
|
16
|
+
# expect { subject }.to change { A.count }
|
17
|
+
# expect { subject }.to not_change { A.count }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# it do
|
22
|
+
# expect { my_method }.to change { A.count }
|
23
|
+
# expect { my_method }.to not_change { A.count }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # also good
|
27
|
+
# it do
|
28
|
+
# expect { subject.a }.to change { A.count }
|
29
|
+
# expect { subject.b }.to not_change { A.count }
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
class RepeatedSubjectCall < Base
|
33
|
+
include TopLevelGroup
|
34
|
+
|
35
|
+
MSG = 'Calls to subject are memoized, this block is misleading'
|
36
|
+
|
37
|
+
# @!method subject?(node)
|
38
|
+
# Find a named or unnamed subject definition
|
39
|
+
#
|
40
|
+
# @example anonymous subject
|
41
|
+
# subject?(parse('subject { foo }').ast) do |name|
|
42
|
+
# name # => :subject
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @example named subject
|
46
|
+
# subject?(parse('subject(:thing) { foo }').ast) do |name|
|
47
|
+
# name # => :thing
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @param node [RuboCop::AST::Node]
|
51
|
+
#
|
52
|
+
# @yield [Symbol] subject name
|
53
|
+
def_node_matcher :subject?, <<-PATTERN
|
54
|
+
(block
|
55
|
+
(send nil?
|
56
|
+
{ #Subjects.all (sym $_) | $#Subjects.all }
|
57
|
+
) args ...)
|
58
|
+
PATTERN
|
59
|
+
|
60
|
+
# @!method subject_calls(node, method_name)
|
61
|
+
def_node_search :subject_calls, <<~PATTERN
|
62
|
+
(send nil? %)
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
def on_top_level_group(node)
|
66
|
+
@subjects_by_node = detect_subjects_in_scope(node)
|
67
|
+
|
68
|
+
detect_offenses_in_block(node)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def detect_offense(subject_node)
|
74
|
+
return if subject_node.chained?
|
75
|
+
return unless (block_node = expect_block(subject_node))
|
76
|
+
|
77
|
+
add_offense(block_node)
|
78
|
+
end
|
79
|
+
|
80
|
+
def expect_block(node)
|
81
|
+
node.each_ancestor(:block).find { |block| block.method?(:expect) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def detect_offenses_in_block(node, subject_names = [])
|
85
|
+
subject_names = [*subject_names, *@subjects_by_node[node]]
|
86
|
+
|
87
|
+
if example?(node)
|
88
|
+
return detect_offenses_in_example(node, subject_names)
|
89
|
+
end
|
90
|
+
|
91
|
+
node.each_child_node(:send, :def, :block, :begin) do |child|
|
92
|
+
detect_offenses_in_block(child, subject_names)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def detect_offenses_in_example(node, subject_names)
|
97
|
+
return unless node.body
|
98
|
+
|
99
|
+
subjects_used = Hash.new(false)
|
100
|
+
|
101
|
+
subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
|
102
|
+
if subjects_used[call.method_name]
|
103
|
+
detect_offense(call)
|
104
|
+
else
|
105
|
+
subjects_used[call.method_name] = true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def detect_subjects_in_scope(node)
|
111
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
112
|
+
subject?(child) do |name|
|
113
|
+
outer_example_group = child.each_ancestor(:block).find do |a|
|
114
|
+
example_group?(a)
|
115
|
+
end
|
116
|
+
|
117
|
+
(h[outer_example_group] ||= []) << name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
|
49
49
|
|
50
50
|
# @!method and_return_value(node)
|
51
|
-
def_node_search :and_return_value,
|
51
|
+
def_node_search :and_return_value, <<~PATTERN
|
52
52
|
$(send _ :and_return $(...))
|
53
53
|
PATTERN
|
54
54
|
|