Skip to content

Commit dfe7463

Browse files
committed
refactor: encapsulate minitest assertion matchers
1 parent 71304ca commit dfe7463

File tree

1 file changed

+149
-109
lines changed

1 file changed

+149
-109
lines changed

lib/rubocop/cop/rspec_rails/minitest_assertions.rb

Lines changed: 149 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -26,115 +26,6 @@ module Rails
2626
class MinitestAssertions < Base
2727
extend AutoCorrector
2828

29-
MSG = 'Use `%<prefer>s`.'
30-
RESTRICT_ON_SEND = %i[
31-
assert_equal
32-
assert_not_equal
33-
assert_instance_of
34-
assert_not_instance_of
35-
assert_includes
36-
assert_not_includes
37-
assert_predicate
38-
assert_not_predicate
39-
assert_match
40-
assert_nil
41-
assert_not_nil
42-
assert_empty
43-
assert_not_empty
44-
refute_equal
45-
refute_instance_of
46-
refute_includes
47-
refute_predicate
48-
refute_nil
49-
refute_empty
50-
refute_match
51-
].freeze
52-
53-
# @!method minitest_equal(node)
54-
def_node_matcher :minitest_equal, <<~PATTERN
55-
(send nil? {:assert_equal :assert_not_equal :refute_equal} $_ $_ $_?)
56-
PATTERN
57-
58-
# @!method minitest_instance_of(node)
59-
def_node_matcher :minitest_instance_of, <<~PATTERN
60-
(send nil? {:assert_instance_of :assert_not_instance_of :refute_instance_of} $_ $_ $_?)
61-
PATTERN
62-
63-
# @!method minitest_includes(node)
64-
def_node_matcher :minitest_includes, <<~PATTERN
65-
(send nil? {:assert_includes :assert_not_includes :refute_includes} $_ $_ $_?)
66-
PATTERN
67-
68-
# @!method minitest_predicate(node)
69-
def_node_matcher :minitest_predicate, <<~PATTERN
70-
(send nil? {:assert_predicate :assert_not_predicate :refute_predicate} $_ ${sym} $_?)
71-
PATTERN
72-
73-
# @!method minitest_match(node)
74-
def_node_matcher :minitest_match, <<~PATTERN
75-
(send nil? {:assert_match :refute_match} $_ $_ $_?)
76-
PATTERN
77-
78-
# @!method minitest_nil(node)
79-
def_node_matcher :minitest_nil, <<~PATTERN
80-
(send nil? {:assert_nil :assert_not_nil :refute_nil} $_ $_?)
81-
PATTERN
82-
83-
# @!method minitest_empty(node)
84-
def_node_matcher :minitest_empty, <<~PATTERN
85-
(send nil? {:assert_empty :assert_not_empty :refute_empty} $_ $_?)
86-
PATTERN
87-
88-
def on_send(node) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
89-
minitest_equal(node) do |expected, actual, failure_message|
90-
on_assertion(node, EqualAssertion.new(expected, actual,
91-
failure_message.first))
92-
end
93-
94-
minitest_instance_of(node) do |expected, actual, failure_message|
95-
on_assertion(node, InstanceOfAssertion.new(expected, actual,
96-
failure_message.first))
97-
end
98-
99-
minitest_predicate(node) do |subject, predicate, failure_message|
100-
next unless predicate.value.end_with?('?')
101-
102-
on_assertion(node, PredicateAssertion.new(predicate, subject,
103-
failure_message.first))
104-
end
105-
106-
minitest_includes(node) do |collection, expected, failure_message|
107-
on_assertion(node, IncludesAssertion.new(expected, collection,
108-
failure_message.first))
109-
end
110-
111-
minitest_match(node) do |matcher, actual, failure_message|
112-
on_assertion(node, MatchAssertion.new(matcher, actual,
113-
failure_message.first))
114-
end
115-
116-
minitest_nil(node) do |actual, failure_message|
117-
on_assertion(node, NilAssertion.new(nil, actual,
118-
failure_message.first))
119-
end
120-
121-
minitest_empty(node) do |actual, failure_message|
122-
on_assertion(node, EmptyAssertion.new(nil, actual,
123-
failure_message.first))
124-
end
125-
end
126-
127-
def on_assertion(node, assertion)
128-
preferred = assertion.replaced(node)
129-
add_offense(node, message: message(preferred)) do |corrector|
130-
corrector.replace(node, preferred)
131-
end
132-
end
133-
134-
def message(preferred)
135-
format(MSG, prefer: preferred)
136-
end
137-
13829
# :nodoc:
13930
class BasicAssertion
14031
def initialize(expected, actual, fail_message)
@@ -151,10 +42,32 @@ def replaced(node)
15142
"expect(#{@actual}).#{runner}(#{assertion}, #{@fail_message})"
15243
end
15344
end
45+
46+
def negated?(node)
47+
raise NotImplementedError
48+
end
49+
50+
def assertion
51+
raise NotImplementedError
52+
end
15453
end
15554

15655
# :nodoc:
15756
class EqualAssertion < BasicAssertion
57+
MATCHERS = %i[
58+
assert_equal
59+
assert_not_equal
60+
refute_equal
61+
].freeze
62+
63+
NODE_MATCHER_PATTERN = <<~PATTERN
64+
(send nil? {:assert_equal :assert_not_equal :refute_equal} $_ $_ $_?)
65+
PATTERN
66+
67+
def self.match(expected, actual, failure_message)
68+
new(expected, actual, failure_message.first)
69+
end
70+
15871
def negated?(node)
15972
!node.method?(:assert_equal)
16073
end
@@ -166,6 +79,20 @@ def assertion
16679

16780
# :nodoc:
16881
class InstanceOfAssertion < BasicAssertion
82+
MATCHERS = %i[
83+
assert_instance_of
84+
assert_not_instance_of
85+
refute_instance_of
86+
].freeze
87+
88+
NODE_MATCHER_PATTERN = <<~PATTERN
89+
(send nil? {:assert_instance_of :assert_not_instance_of :refute_instance_of} $_ $_ $_?)
90+
PATTERN
91+
92+
def self.match(expected, actual, failure_message)
93+
new(expected, actual, failure_message.first)
94+
end
95+
16996
def negated?(node)
17097
!node.method?(:assert_instance_of)
17198
end
@@ -177,6 +104,20 @@ def assertion
177104

178105
# :nodoc:
179106
class IncludesAssertion < BasicAssertion
107+
MATCHERS = %i[
108+
assert_includes
109+
assert_not_includes
110+
refute_includes
111+
].freeze
112+
113+
NODE_MATCHER_PATTERN = <<~PATTERN
114+
(send nil? {:assert_includes :assert_not_includes :refute_includes} $_ $_ $_?)
115+
PATTERN
116+
117+
def self.match(collection, expected, failure_message)
118+
new(expected, collection, failure_message.first)
119+
end
120+
180121
def negated?(node)
181122
!node.method?(:assert_includes)
182123
end
@@ -188,6 +129,22 @@ def assertion
188129

189130
# :nodoc:
190131
class PredicateAssertion < BasicAssertion
132+
MATCHERS = %i[
133+
assert_predicate
134+
assert_not_predicate
135+
refute_predicate
136+
].freeze
137+
138+
NODE_MATCHER_PATTERN = <<~PATTERN
139+
(send nil? {:assert_predicate :assert_not_predicate :refute_predicate} $_ ${sym} $_?)
140+
PATTERN
141+
142+
def self.match(subject, predicate, failure_message)
143+
return nil unless predicate.value.end_with?('?')
144+
145+
new(predicate, subject, failure_message.first)
146+
end
147+
191148
def negated?(node)
192149
!node.method?(:assert_predicate)
193150
end
@@ -199,6 +156,19 @@ def assertion
199156

200157
# :nodoc:
201158
class MatchAssertion < BasicAssertion
159+
MATCHERS = %i[
160+
assert_match
161+
refute_match
162+
].freeze
163+
164+
NODE_MATCHER_PATTERN = <<~PATTERN
165+
(send nil? {:assert_match :refute_match} $_ $_ $_?)
166+
PATTERN
167+
168+
def self.match(matcher, actual, failure_message)
169+
new(matcher, actual, failure_message.first)
170+
end
171+
202172
def negated?(node)
203173
!node.method?(:assert_match)
204174
end
@@ -210,6 +180,20 @@ def assertion
210180

211181
# :nodoc:
212182
class NilAssertion < BasicAssertion
183+
MATCHERS = %i[
184+
assert_nil
185+
assert_not_nil
186+
refute_nil
187+
].freeze
188+
189+
NODE_MATCHER_PATTERN = <<~PATTERN
190+
(send nil? {:assert_nil :assert_not_nil :refute_nil} $_ $_?)
191+
PATTERN
192+
193+
def self.match(actual, failure_message)
194+
new(nil, actual, failure_message.first)
195+
end
196+
213197
def negated?(node)
214198
!node.method?(:assert_nil)
215199
end
@@ -221,6 +205,20 @@ def assertion
221205

222206
# :nodoc:
223207
class EmptyAssertion < BasicAssertion
208+
MATCHERS = %i[
209+
assert_empty
210+
assert_not_empty
211+
refute_empty
212+
].freeze
213+
214+
NODE_MATCHER_PATTERN = <<~PATTERN
215+
(send nil? {:assert_empty :assert_not_empty :refute_empty} $_ $_?)
216+
PATTERN
217+
218+
def self.match(actual, failure_message)
219+
new(nil, actual, failure_message.first)
220+
end
221+
224222
def negated?(node)
225223
!node.method?(:assert_empty)
226224
end
@@ -229,6 +227,48 @@ def assertion
229227
'be_empty'
230228
end
231229
end
230+
231+
MSG = 'Use `%<prefer>s`.'
232+
233+
# TODO: replace with `BasicAssertion.subclasses` in Ruby 3.1+
234+
ASSERTION_MATCHERS = constants(false).filter_map do |c|
235+
const = const_get(c)
236+
237+
const if const.is_a?(Class) && const.superclass == BasicAssertion
238+
end
239+
240+
RESTRICT_ON_SEND = ASSERTION_MATCHERS.flat_map { |m| m::MATCHERS }
241+
242+
ASSERTION_MATCHERS.each do |m|
243+
name = m.name.split('::').last
244+
245+
def_node_matcher "minitest_#{name}".to_sym, m::NODE_MATCHER_PATTERN
246+
end
247+
248+
def on_send(node)
249+
ASSERTION_MATCHERS.each do |m|
250+
name = m.name.split('::').last
251+
252+
public_send("minitest_#{name}".to_sym, node) do |*args|
253+
assertion = m.match(*args)
254+
255+
next if assertion.nil?
256+
257+
on_assertion(node, assertion)
258+
end
259+
end
260+
end
261+
262+
def on_assertion(node, assertion)
263+
preferred = assertion.replaced(node)
264+
add_offense(node, message: message(preferred)) do |corrector|
265+
corrector.replace(node, preferred)
266+
end
267+
end
268+
269+
def message(preferred)
270+
format(MSG, prefer: preferred)
271+
end
232272
end
233273
end
234274
end

0 commit comments

Comments
 (0)