rubocop-minitest 0.6.2 → 0.10.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.
- checksums.yaml +5 -5
- data/.circleci/config.yml +0 -3
- data/.gitattributes +1 -0
- data/.rubocop.yml +2 -1
- data/.rubocop_todo.yml +0 -7
- data/CHANGELOG.md +65 -0
- data/Gemfile +1 -1
- data/README.md +5 -1
- data/Rakefile +29 -0
- data/config/default.yml +103 -18
- data/docs/antora.yml +7 -0
- data/docs/modules/ROOT/nav.adoc +6 -0
- data/docs/modules/ROOT/pages/cops.adoc +37 -0
- data/docs/modules/ROOT/pages/cops_minitest.adoc +1014 -0
- data/docs/modules/ROOT/pages/index.adoc +5 -0
- data/docs/modules/ROOT/pages/installation.adoc +15 -0
- data/docs/modules/ROOT/pages/usage.adoc +32 -0
- data/{manual → legacy-docs}/cops.md +1 -0
- data/{manual → legacy-docs}/cops_minitest.md +64 -41
- data/{manual → legacy-docs}/index.md +0 -0
- data/{manual → legacy-docs}/installation.md +0 -0
- data/{manual → legacy-docs}/usage.md +0 -0
- data/lib/rubocop/cop/generator.rb +56 -0
- data/lib/rubocop/cop/minitest/assert_empty.rb +4 -30
- data/lib/rubocop/cop/minitest/assert_empty_literal.rb +15 -0
- data/lib/rubocop/cop/minitest/assert_equal.rb +2 -31
- data/lib/rubocop/cop/minitest/assert_in_delta.rb +27 -0
- data/lib/rubocop/cop/minitest/assert_includes.rb +4 -4
- data/lib/rubocop/cop/minitest/assert_instance_of.rb +4 -38
- data/lib/rubocop/cop/minitest/assert_kind_of.rb +25 -0
- data/lib/rubocop/cop/minitest/assert_match.rb +4 -39
- data/lib/rubocop/cop/minitest/assert_nil.rb +2 -2
- data/lib/rubocop/cop/minitest/assert_output.rb +49 -0
- data/lib/rubocop/cop/minitest/assert_path_exists.rb +58 -0
- data/lib/rubocop/cop/minitest/assert_respond_to.rb +10 -45
- data/lib/rubocop/cop/minitest/assert_silent.rb +45 -0
- data/lib/rubocop/cop/minitest/assert_truthy.rb +2 -2
- data/lib/rubocop/cop/minitest/assertion_in_lifecycle_hook.rb +43 -0
- data/lib/rubocop/cop/minitest/global_expectations.rb +95 -0
- data/lib/rubocop/cop/minitest/literal_as_actual_argument.rb +52 -0
- data/lib/rubocop/cop/minitest/multiple_assertions.rb +63 -0
- data/lib/rubocop/cop/minitest/refute_empty.rb +4 -30
- data/lib/rubocop/cop/minitest/refute_false.rb +3 -3
- data/lib/rubocop/cop/minitest/refute_in_delta.rb +27 -0
- data/lib/rubocop/cop/minitest/refute_includes.rb +4 -4
- data/lib/rubocop/cop/minitest/refute_instance_of.rb +4 -38
- data/lib/rubocop/cop/minitest/refute_kind_of.rb +25 -0
- data/lib/rubocop/cop/minitest/refute_match.rb +4 -39
- data/lib/rubocop/cop/minitest/refute_nil.rb +2 -2
- data/lib/rubocop/cop/minitest/refute_path_exists.rb +58 -0
- data/lib/rubocop/cop/minitest/refute_respond_to.rb +10 -45
- data/lib/rubocop/cop/minitest/test_method_name.rb +70 -0
- data/lib/rubocop/cop/minitest/unspecified_exception.rb +36 -0
- data/lib/rubocop/cop/minitest_cops.rb +17 -1
- data/lib/rubocop/cop/mixin/argument_range_helper.rb +10 -0
- data/lib/rubocop/cop/mixin/in_delta_mixin.rb +50 -0
- data/lib/rubocop/cop/mixin/minitest_cop_rule.rb +102 -0
- data/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +84 -0
- data/lib/rubocop/minitest/version.rb +1 -1
- data/mkdocs.yml +2 -2
- data/relnotes/v0.10.0.md +21 -0
- data/relnotes/v0.6.2.md +5 -0
- data/relnotes/v0.7.0.md +13 -0
- data/relnotes/v0.8.0.md +12 -0
- data/relnotes/v0.8.1.md +5 -0
- data/relnotes/v0.9.0.md +10 -0
- data/rubocop-minitest.gemspec +4 -4
- data/tasks/cops_documentation.rake +83 -52
- data/tasks/cut_release.rake +16 -0
- metadata +45 -15
- data/lib/rubocop/cop/mixin/includes_cop_rule.rb +0 -78
@@ -9,11 +9,11 @@ module RuboCop
|
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
11
|
# assert_equal(true, actual)
|
12
|
-
# assert_equal(true, actual, '
|
12
|
+
# assert_equal(true, actual, 'message')
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# assert(actual)
|
16
|
-
# assert(actual, '
|
16
|
+
# assert(actual, 'message')
|
17
17
|
#
|
18
18
|
class AssertTruthy < Cop
|
19
19
|
include ArgumentRangeHelper
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop checks for usage of assertions in lifecycle hooks.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# class FooTest < Minitest::Test
|
11
|
+
# def setup
|
12
|
+
# assert_equal(foo, bar)
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# class FooTest < Minitest::Test
|
18
|
+
# def test_something
|
19
|
+
# assert_equal(foo, bar)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
class AssertionInLifecycleHook < Cop
|
24
|
+
include MinitestExplorationHelpers
|
25
|
+
|
26
|
+
MSG = 'Do not use `%<assertion>s` in `%<hook>s` hook.'
|
27
|
+
|
28
|
+
def on_class(class_node)
|
29
|
+
return unless test_class?(class_node)
|
30
|
+
|
31
|
+
lifecycle_hooks(class_node).each do |hook_node|
|
32
|
+
hook_node.each_descendant(:send) do |node|
|
33
|
+
if assertion?(node)
|
34
|
+
message = format(MSG, assertion: node.method_name, hook: hook_node.method_name)
|
35
|
+
add_offense(node, message: message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop checks for deprecated global expectations
|
7
|
+
# and autocorrects them to use expect format.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# musts.must_equal expected_musts
|
12
|
+
# wonts.wont_match expected_wonts
|
13
|
+
# musts.must_raise TypeError
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# _(musts).must_equal expected_musts
|
17
|
+
# _(wonts).wont_match expected_wonts
|
18
|
+
# _ { musts }.must_raise TypeError
|
19
|
+
class GlobalExpectations < Cop
|
20
|
+
MSG = 'Use `%<preferred>s` instead.'
|
21
|
+
|
22
|
+
VALUE_MATCHERS = %i[
|
23
|
+
must_be_empty must_equal must_be_close_to must_be_within_delta
|
24
|
+
must_be_within_epsilon must_include must_be_instance_of must_be_kind_of
|
25
|
+
must_match must_be_nil must_be must_respond_to must_be_same_as
|
26
|
+
path_must_exist path_wont_exist wont_be_empty wont_equal wont_be_close_to
|
27
|
+
wont_be_within_delta wont_be_within_epsilon wont_include wont_be_instance_of
|
28
|
+
wont_be_kind_of wont_match wont_be_nil wont_be wont_respond_to wont_be_same_as
|
29
|
+
].freeze
|
30
|
+
|
31
|
+
BLOCK_MATCHERS = %i[must_output must_raise must_be_silent must_throw].freeze
|
32
|
+
|
33
|
+
VALUE_MATCHERS_STR = VALUE_MATCHERS.map do |m|
|
34
|
+
":#{m}"
|
35
|
+
end.join(' ').freeze
|
36
|
+
|
37
|
+
BLOCK_MATCHERS_STR = BLOCK_MATCHERS.map do |m|
|
38
|
+
":#{m}"
|
39
|
+
end.join(' ').freeze
|
40
|
+
|
41
|
+
# There are aliases for the `_` method - `expect` and `value`
|
42
|
+
DSL_METHODS_LIST = %w[_ value expect].map do |n|
|
43
|
+
":#{n}"
|
44
|
+
end.join(' ').freeze
|
45
|
+
|
46
|
+
def_node_matcher :value_global_expectation?, <<~PATTERN
|
47
|
+
(send !(send nil? {#{DSL_METHODS_LIST}} _) {#{VALUE_MATCHERS_STR}} ...)
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
def_node_matcher :block_global_expectation?, <<~PATTERN
|
51
|
+
(send
|
52
|
+
[
|
53
|
+
!(send nil? {#{DSL_METHODS_LIST}} _)
|
54
|
+
!(block (send nil? {#{DSL_METHODS_LIST}}) _ _)
|
55
|
+
]
|
56
|
+
{#{BLOCK_MATCHERS_STR}}
|
57
|
+
_
|
58
|
+
)
|
59
|
+
PATTERN
|
60
|
+
|
61
|
+
def on_send(node)
|
62
|
+
return unless value_global_expectation?(node) || block_global_expectation?(node)
|
63
|
+
|
64
|
+
message = format(MSG, preferred: preferred_receiver(node))
|
65
|
+
add_offense(node, location: node.receiver.source_range, message: message)
|
66
|
+
end
|
67
|
+
|
68
|
+
def autocorrect(node)
|
69
|
+
return unless value_global_expectation?(node) || block_global_expectation?(node)
|
70
|
+
|
71
|
+
lambda do |corrector|
|
72
|
+
receiver = node.receiver.source_range
|
73
|
+
|
74
|
+
if BLOCK_MATCHERS.include?(node.method_name)
|
75
|
+
corrector.wrap(receiver, '_ { ', ' }')
|
76
|
+
else
|
77
|
+
corrector.wrap(receiver, '_(', ')')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def preferred_receiver(node)
|
85
|
+
source = node.receiver.source
|
86
|
+
if BLOCK_MATCHERS.include?(node.method_name)
|
87
|
+
"_ { #{source} }"
|
88
|
+
else
|
89
|
+
"_(#{source})"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces correct order of expected and
|
7
|
+
# actual arguments for `assert_equal`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# assert_equal foo, 2
|
12
|
+
# assert_equal foo, [1, 2]
|
13
|
+
# assert_equal foo, [1, 2], 'message'
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# assert_equal 2, foo
|
17
|
+
# assert_equal [1, 2], foo
|
18
|
+
# assert_equal [1, 2], foo, 'message'
|
19
|
+
#
|
20
|
+
class LiteralAsActualArgument < Cop
|
21
|
+
include ArgumentRangeHelper
|
22
|
+
|
23
|
+
MSG = 'Replace the literal with the first argument.'
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
return unless node.method?(:assert_equal)
|
27
|
+
|
28
|
+
actual = node.arguments[1]
|
29
|
+
return unless actual
|
30
|
+
|
31
|
+
add_offense(node, location: all_arguments_range(node)) if actual.recursive_basic_literal?
|
32
|
+
end
|
33
|
+
|
34
|
+
def autocorrect(node)
|
35
|
+
expected, actual, message = *node.arguments
|
36
|
+
|
37
|
+
lambda do |corrector|
|
38
|
+
new_actual_source =
|
39
|
+
if actual.hash_type? && !actual.braces?
|
40
|
+
"{#{actual.source}}"
|
41
|
+
else
|
42
|
+
actual.source
|
43
|
+
end
|
44
|
+
arguments = [new_actual_source, expected.source, message&.source].compact.join(', ')
|
45
|
+
|
46
|
+
corrector.replace(node, "assert_equal(#{arguments})")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop checks if test cases contain too many assertion calls.
|
7
|
+
# The maximum allowed assertion calls is configurable.
|
8
|
+
#
|
9
|
+
# @example Max: 1
|
10
|
+
# # bad
|
11
|
+
# class FooTest < Minitest::Test
|
12
|
+
# def test_asserts_twice
|
13
|
+
# assert_equal(42, do_something)
|
14
|
+
# assert_empty(array)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# class FooTest < Minitest::Test
|
20
|
+
# def test_asserts_once
|
21
|
+
# assert_equal(42, do_something)
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def test_another_asserts_once
|
25
|
+
# assert_empty(array)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
class MultipleAssertions < Cop
|
30
|
+
include ConfigurableMax
|
31
|
+
include MinitestExplorationHelpers
|
32
|
+
|
33
|
+
MSG = 'Test case has too many assertions [%<total>d/%<max>d].'
|
34
|
+
|
35
|
+
def on_class(class_node)
|
36
|
+
return unless test_class?(class_node)
|
37
|
+
|
38
|
+
test_cases(class_node).each do |node|
|
39
|
+
assertions_count = assertions_count(node)
|
40
|
+
|
41
|
+
next unless assertions_count > max_assertions
|
42
|
+
|
43
|
+
self.max = assertions_count
|
44
|
+
|
45
|
+
message = format(MSG, total: assertions_count, max: max_assertions)
|
46
|
+
add_offense(node, location: :name, message: message)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def assertions_count(node)
|
53
|
+
base = assertion?(node) ? 1 : 0
|
54
|
+
base + node.each_child_node.sum { |c| assertions_count(c) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def max_assertions
|
58
|
+
Integer(cop_config.fetch('Max', 3))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -9,42 +9,16 @@ module RuboCop
|
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
11
|
# refute(object.empty?)
|
12
|
-
# refute(object.empty?, '
|
12
|
+
# refute(object.empty?, 'message')
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# refute_empty(object)
|
16
|
-
# refute_empty(object, '
|
16
|
+
# refute_empty(object, 'message')
|
17
17
|
#
|
18
18
|
class RefuteEmpty < Cop
|
19
|
-
|
19
|
+
extend MinitestCopRule
|
20
20
|
|
21
|
-
|
22
|
-
'`refute(%<receiver>s)`.'
|
23
|
-
|
24
|
-
def_node_matcher :refute_with_empty, <<~PATTERN
|
25
|
-
(send nil? :refute $(send $_ :empty?) $...)
|
26
|
-
PATTERN
|
27
|
-
|
28
|
-
def on_send(node)
|
29
|
-
refute_with_empty(node) do |first_receiver_arg, object, rest_receiver_arg|
|
30
|
-
message = rest_receiver_arg.first
|
31
|
-
|
32
|
-
arguments = [object.source, message&.source].compact.join(', ')
|
33
|
-
receiver = [first_receiver_arg.source, message&.source].compact.join(', ')
|
34
|
-
|
35
|
-
offense_message = format(MSG, arguments: arguments, receiver: receiver)
|
36
|
-
add_offense(node, message: offense_message)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def autocorrect(node)
|
41
|
-
lambda do |corrector|
|
42
|
-
refute_with_empty(node) do |_, actual_arg|
|
43
|
-
corrector.replace(node.loc.selector, 'refute_empty')
|
44
|
-
corrector.replace(first_argument_range(node), actual_arg.source)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
21
|
+
define_rule :refute, target_method: :empty?
|
48
22
|
end
|
49
23
|
end
|
50
24
|
end
|
@@ -9,14 +9,14 @@ module RuboCop
|
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
11
|
# assert_equal(false, actual)
|
12
|
-
# assert_equal(false, actual, '
|
12
|
+
# assert_equal(false, actual, 'message')
|
13
13
|
#
|
14
14
|
# assert(!test)
|
15
|
-
# assert(!test, '
|
15
|
+
# assert(!test, 'message')
|
16
16
|
#
|
17
17
|
# # good
|
18
18
|
# refute(actual)
|
19
|
-
# refute(actual, '
|
19
|
+
# refute(actual, 'message')
|
20
20
|
#
|
21
21
|
class RefuteFalse < Cop
|
22
22
|
include ArgumentRangeHelper
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces the test to use `refute_in_delta`
|
7
|
+
# instead of using `refute_equal` to compare floats.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# refute_equal(0.2, actual)
|
12
|
+
# refute_equal(0.2, actual, 'message')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# refute_in_delta(0.2, actual)
|
16
|
+
# refute_in_delta(0.2, actual, 0.001, 'message')
|
17
|
+
#
|
18
|
+
class RefuteInDelta < Cop
|
19
|
+
include InDeltaMixin
|
20
|
+
|
21
|
+
def_node_matcher :equal_floats_call, <<~PATTERN
|
22
|
+
(send nil? :refute_equal $_ $_ $...)
|
23
|
+
PATTERN
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -9,16 +9,16 @@ module RuboCop
|
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
11
|
# refute(collection.include?(object))
|
12
|
-
# refute(collection.include?(object), '
|
12
|
+
# refute(collection.include?(object), 'message')
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# refute_includes(collection, object)
|
16
|
-
# refute_includes(collection, object, '
|
16
|
+
# refute_includes(collection, object, 'message')
|
17
17
|
#
|
18
18
|
class RefuteIncludes < Cop
|
19
|
-
extend
|
19
|
+
extend MinitestCopRule
|
20
20
|
|
21
|
-
|
21
|
+
define_rule :refute, target_method: :include?, preferred_method: :refute_includes
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -9,50 +9,16 @@ module RuboCop
|
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
11
|
# refute(object.instance_of?(Class))
|
12
|
-
# refute(object.instance_of?(Class), '
|
12
|
+
# refute(object.instance_of?(Class), 'message')
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# refute_instance_of(Class, object)
|
16
|
-
# refute_instance_of(Class, object, '
|
16
|
+
# refute_instance_of(Class, object, 'message')
|
17
17
|
#
|
18
18
|
class RefuteInstanceOf < Cop
|
19
|
-
|
19
|
+
extend MinitestCopRule
|
20
20
|
|
21
|
-
|
22
|
-
'`refute(%<receiver>s)`.'
|
23
|
-
|
24
|
-
def_node_matcher :refute_with_instance_of, <<~PATTERN
|
25
|
-
(send nil? :refute $(send $_ :instance_of? $_) $...)
|
26
|
-
PATTERN
|
27
|
-
|
28
|
-
def on_send(node)
|
29
|
-
refute_with_instance_of(node) do |first_receiver_arg, object, method, rest_args|
|
30
|
-
message = rest_args.first
|
31
|
-
arguments = node_arguments(object, method, message)
|
32
|
-
receiver = [first_receiver_arg.source, message&.source].compact.join(', ')
|
33
|
-
|
34
|
-
offense_message = format(MSG, arguments: arguments, receiver: receiver)
|
35
|
-
|
36
|
-
add_offense(node, message: offense_message)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def autocorrect(node)
|
41
|
-
lambda do |corrector|
|
42
|
-
refute_with_instance_of(node) do |_, object, method|
|
43
|
-
corrector.replace(node.loc.selector, 'refute_instance_of')
|
44
|
-
|
45
|
-
replacement = [method, object].map(&:source).join(', ')
|
46
|
-
corrector.replace(first_argument_range(node), replacement)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def node_arguments(object, method, message)
|
54
|
-
[method, object, message].compact.map(&:source).join(', ')
|
55
|
-
end
|
21
|
+
define_rule :refute, target_method: :instance_of?, inverse: true
|
56
22
|
end
|
57
23
|
end
|
58
24
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces the use of `refute_kind_of(Class, object)`
|
7
|
+
# over `refute(object.kind_of?(Class))`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# refute(object.kind_of?(Class))
|
12
|
+
# refute(object.kind_of?(Class), 'message')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# refute_kind_of(Class, object)
|
16
|
+
# refute_kind_of(Class, object, 'message')
|
17
|
+
#
|
18
|
+
class RefuteKindOf < Cop
|
19
|
+
extend MinitestCopRule
|
20
|
+
|
21
|
+
define_rule :refute, target_method: :kind_of?, inverse: true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|