rubocop-rspec 2.26.0 → 2.27.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/config/default.yml +18 -1
- data/lib/rubocop/cop/rspec/be_empty.rb +1 -0
- data/lib/rubocop/cop/rspec/change_by_zero.rb +27 -1
- data/lib/rubocop/cop/rspec/described_class.rb +3 -2
- data/lib/rubocop/cop/rspec/example_without_description.rb +11 -2
- data/lib/rubocop/cop/rspec/expect_actual.rb +5 -2
- data/lib/rubocop/cop/rspec/expect_output.rb +1 -4
- data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
- data/lib/rubocop/cop/rspec/message_expectation.rb +0 -1
- data/lib/rubocop/cop/rspec/message_spies.rb +0 -2
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +10 -5
- data/lib/rubocop/cop/rspec/rails/have_http_status.rb +33 -9
- data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +314 -22
- data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +4 -2
- data/lib/rubocop/cop/rspec/repeated_subject_call.rb +120 -0
- data/lib/rubocop/cop/rspec/shared_examples.rb +3 -4
- data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +1 -2
- data/lib/rubocop/cop/rspec_cops.rb +2 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66ac0fecdd858c4b85a06c9e32bdb680d2dd92aa98100740302702a1737a3a9c
|
4
|
+
data.tar.gz: 5f688cf15390149d3a7dc6e2d3f67e041248ee43a3dce5e47d2370d2813f86d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fc186d4d8191c461dee977e64a2f0a295389be99a8054bb7ba75258e429c5c3dc624b3d6da0a19b65a024648f1922ef9646af5bac44d0b7c3255a99ac8e14bc
|
7
|
+
data.tar.gz: 1d603f51d43de587cd17939252604f1d86d371e0e6883a4289c219eed97d0862bd4ba29d2c3ba205ceed3c4157692d4fbbf52b63d020facfd0d2798bef284e55
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
## Master (Unreleased)
|
4
4
|
|
5
|
+
## 2.27.0 (2024-03-01)
|
6
|
+
|
7
|
+
- Add new `RSpec/IsExpectedSpecify` cop. ([@ydah])
|
8
|
+
- Add new `RSpec/RepeatedSubjectCall` cop. ([@drcapulet])
|
9
|
+
- Add support for `assert_true`, `assert_false`, `assert_not_equal`, `assert_not_nil`, `*_empty`, `*_predicate`, `*_kind_of`, `*_in_delta`, `*_match`, `*_instance_of` and `*_includes` assertions in `RSpec/Rails/MinitestAssertions`. ([@ydah], [@G-Rath])
|
10
|
+
- Support asserts with messages in `Rspec/BeEmpty`. ([@G-Rath])
|
11
|
+
- Fix a false positive for `RSpec/ExpectActual` when used with rspec-rails routing matchers. ([@naveg])
|
12
|
+
- Add configuration option `ResponseMethods` to `RSpec/Rails/HaveHttpStatus`. ([@ydah])
|
13
|
+
- Fix a false negative for `RSpec/DescribedClass` when class with constant. ([@ydah])
|
14
|
+
- Fix a false positive for `RSpec/ExampleWithoutDescription` when `specify` with multi-line block and missing description. ([@ydah])
|
15
|
+
- Fix an incorrect autocorrect for `RSpec/ChangeByZero` when compound expectations with line break before `.by(0)`. ([@ydah])
|
16
|
+
|
17
|
+
## 2.26.1 (2024-01-05)
|
18
|
+
|
19
|
+
- Fix an error for `RSpec/SharedExamples` when using examples without argument. ([@ydah])
|
20
|
+
|
5
21
|
## 2.26.0 (2024-01-04)
|
6
22
|
|
7
23
|
- Add new `RSpec/RedundantPredicateMatcher` cop. ([@ydah])
|
@@ -841,6 +857,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
841
857
|
[@deivid-rodriguez]: https://github.com/deivid-rodriguez
|
842
858
|
[@dgollahon]: https://github.com/dgollahon
|
843
859
|
[@dmitrytsepelev]: https://github.com/dmitrytsepelev
|
860
|
+
[@drcapulet]: https://github.com/drcapulet
|
844
861
|
[@drowze]: https://github.com/Drowze
|
845
862
|
[@dswij]: https://github.com/dswij
|
846
863
|
[@dvandersluis]: https://github.com/dvandersluis
|
@@ -888,6 +905,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
888
905
|
[@mockdeep]: https://github.com/mockdeep
|
889
906
|
[@mothonmars]: https://github.com/MothOnMars
|
890
907
|
[@mvz]: https://github.com/mvz
|
908
|
+
[@naveg]: https://github.com/naveg
|
891
909
|
[@nc-holodakg]: https://github.com/nc-holodakg
|
892
910
|
[@nevir]: https://github.com/nevir
|
893
911
|
[@ngouy]: https://github.com/ngouy
|
data/config/default.yml
CHANGED
@@ -548,6 +548,13 @@ RSpec/InstanceVariable:
|
|
548
548
|
StyleGuide: https://rspec.rubystyle.guide/#instance-variables
|
549
549
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/InstanceVariable
|
550
550
|
|
551
|
+
RSpec/IsExpectedSpecify:
|
552
|
+
Description: Check for `specify` with `is_expected` and one-liner expectations.
|
553
|
+
Enabled: pending
|
554
|
+
VersionAdded: '2.27'
|
555
|
+
StyleGuide: https://rspec.rubystyle.guide/#it-and-specify
|
556
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IsExpectedSpecify
|
557
|
+
|
551
558
|
RSpec/ItBehavesLike:
|
552
559
|
Description: Checks that only one `it_behaves_like` style is used.
|
553
560
|
Enabled: true
|
@@ -813,6 +820,12 @@ RSpec/RepeatedIncludeExample:
|
|
813
820
|
VersionAdded: '1.44'
|
814
821
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedIncludeExample
|
815
822
|
|
823
|
+
RSpec/RepeatedSubjectCall:
|
824
|
+
Description: Checks for repeated calls to subject missing that it is memoized.
|
825
|
+
Enabled: pending
|
826
|
+
VersionAdded: '2.27'
|
827
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedSubjectCall
|
828
|
+
|
816
829
|
RSpec/ReturnFromStub:
|
817
830
|
Description: Checks for consistent style of stub's return setting.
|
818
831
|
Enabled: true
|
@@ -1126,8 +1139,12 @@ RSpec/Rails/AvoidSetupHook:
|
|
1126
1139
|
RSpec/Rails/HaveHttpStatus:
|
1127
1140
|
Description: Checks that tests use `have_http_status` instead of equality matchers.
|
1128
1141
|
Enabled: pending
|
1142
|
+
ResponseMethods:
|
1143
|
+
- response
|
1144
|
+
- last_response
|
1129
1145
|
SafeAutoCorrect: false
|
1130
1146
|
VersionAdded: '2.12'
|
1147
|
+
VersionChanged: '2.27'
|
1131
1148
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus
|
1132
1149
|
|
1133
1150
|
RSpec/Rails/HttpStatus:
|
@@ -1166,7 +1183,7 @@ RSpec/Rails/InferredSpecType:
|
|
1166
1183
|
views: view
|
1167
1184
|
|
1168
1185
|
RSpec/Rails/MinitestAssertions:
|
1169
|
-
Description: Check if using Minitest matchers.
|
1186
|
+
Description: Check if using Minitest-like matchers.
|
1170
1187
|
Enabled: pending
|
1171
1188
|
VersionAdded: '2.17'
|
1172
1189
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions
|
@@ -59,6 +59,8 @@ module RuboCop
|
|
59
59
|
#
|
60
60
|
class ChangeByZero < Base
|
61
61
|
extend AutoCorrector
|
62
|
+
include RangeHelp
|
63
|
+
|
62
64
|
MSG = 'Prefer `not_to change` over `to %<method>s.by(0)`.'
|
63
65
|
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
64
66
|
'over `%<method>s.by(0)`.'
|
@@ -140,8 +142,32 @@ module RuboCop
|
|
140
142
|
|
141
143
|
change_nodes(node) do |change_node|
|
142
144
|
corrector.replace(change_node.loc.selector, negated_matcher)
|
143
|
-
|
145
|
+
insert_operator(corrector, node, change_node)
|
146
|
+
remove_by_zero(corrector, node, change_node)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def insert_operator(corrector, node, change_node)
|
151
|
+
operator = node.right_siblings.first
|
152
|
+
return unless %i[& |].include?(operator)
|
153
|
+
|
154
|
+
corrector.insert_after(
|
155
|
+
replace_node(node, change_node), " #{operator}"
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
def replace_node(node, change_node)
|
160
|
+
expect_change_with_arguments(node) ? change_node : change_node.parent
|
161
|
+
end
|
162
|
+
|
163
|
+
def remove_by_zero(corrector, node, change_node)
|
164
|
+
range = node.loc.dot.with(end_pos: node.source_range.end_pos)
|
165
|
+
if change_node.loc.line == range.line
|
144
166
|
corrector.remove(range)
|
167
|
+
else
|
168
|
+
corrector.remove(
|
169
|
+
range_by_whole_lines(range, include_final_newline: true)
|
170
|
+
)
|
145
171
|
end
|
146
172
|
end
|
147
173
|
|
@@ -113,7 +113,7 @@ module RuboCop
|
|
113
113
|
def find_usage(node, &block)
|
114
114
|
yield(node) if offensive?(node)
|
115
115
|
|
116
|
-
return if scope_change?(node)
|
116
|
+
return if scope_change?(node)
|
117
117
|
|
118
118
|
node.each_child_node do |child|
|
119
119
|
find_usage(child, &block)
|
@@ -194,7 +194,8 @@ module RuboCop
|
|
194
194
|
# const_name(s(:const, s(:const, nil, :M), :C)) # => [:M, :C]
|
195
195
|
# const_name(s(:const, s(:cbase), :C)) # => [nil, :C]
|
196
196
|
def const_name(node)
|
197
|
-
namespace
|
197
|
+
namespace = node.namespace
|
198
|
+
name = node.short_name
|
198
199
|
if !namespace
|
199
200
|
[name]
|
200
201
|
elsif namespace.const_type?
|
@@ -7,6 +7,7 @@ module RuboCop
|
|
7
7
|
#
|
8
8
|
# RSpec allows for auto-generated example descriptions when there is no
|
9
9
|
# description provided or the description is an empty one.
|
10
|
+
# It is acceptable to use `specify` without a description
|
10
11
|
#
|
11
12
|
# This cop removes empty descriptions.
|
12
13
|
# It also defines whether auto-generated description is allowed, based
|
@@ -14,17 +15,24 @@ module RuboCop
|
|
14
15
|
#
|
15
16
|
# This cop can be configured using the `EnforcedStyle` option
|
16
17
|
#
|
18
|
+
# @example
|
19
|
+
# # always good
|
20
|
+
# specify do
|
21
|
+
# result = service.call
|
22
|
+
# expect(result).to be(true)
|
23
|
+
# end
|
24
|
+
#
|
17
25
|
# @example `EnforcedStyle: always_allow` (default)
|
18
26
|
# # bad
|
19
27
|
# it('') { is_expected.to be_good }
|
20
|
-
#
|
28
|
+
# specify '' do
|
21
29
|
# result = service.call
|
22
30
|
# expect(result).to be(true)
|
23
31
|
# end
|
24
32
|
#
|
25
33
|
# # good
|
26
34
|
# it { is_expected.to be_good }
|
27
|
-
#
|
35
|
+
# specify do
|
28
36
|
# result = service.call
|
29
37
|
# expect(result).to be(true)
|
30
38
|
# end
|
@@ -75,6 +83,7 @@ module RuboCop
|
|
75
83
|
def check_example_without_description(node)
|
76
84
|
return if node.arguments?
|
77
85
|
return unless disallow_empty_description?(node)
|
86
|
+
return if node.method?(:specify) && node.parent.multiline?
|
78
87
|
|
79
88
|
add_offense(node, message: MSG_ADD_DESCRIPTION)
|
80
89
|
end
|
@@ -50,7 +50,8 @@ module RuboCop
|
|
50
50
|
regexp
|
51
51
|
].freeze
|
52
52
|
|
53
|
-
|
53
|
+
SKIPPED_MATCHERS = %i[route_to be_routable].freeze
|
54
|
+
CORRECTABLE_MATCHERS = %i[eq eql equal be].freeze
|
54
55
|
|
55
56
|
# @!method expect_literal(node)
|
56
57
|
def_node_matcher :expect_literal, <<~PATTERN
|
@@ -66,8 +67,10 @@ module RuboCop
|
|
66
67
|
|
67
68
|
def on_send(node)
|
68
69
|
expect_literal(node) do |actual, matcher, expected|
|
70
|
+
next if SKIPPED_MATCHERS.include?(matcher)
|
71
|
+
|
69
72
|
add_offense(actual.source_range) do |corrector|
|
70
|
-
next unless
|
73
|
+
next unless CORRECTABLE_MATCHERS.include?(matcher)
|
71
74
|
next if literal?(expected)
|
72
75
|
|
73
76
|
swap(corrector, actual, expected)
|
@@ -22,10 +22,7 @@ module RuboCop
|
|
22
22
|
def on_gvasgn(node)
|
23
23
|
return unless inside_example_scope?(node)
|
24
24
|
|
25
|
-
|
26
|
-
variable_name, _rhs = *node
|
27
|
-
# rubocop:enable InternalAffairs/NodeDestructuring
|
28
|
-
name = variable_name[1..]
|
25
|
+
name = node.name[1..]
|
29
26
|
return unless name.eql?('stdout') || name.eql?('stderr')
|
30
27
|
|
31
28
|
add_offense(node.loc.name, message: format(MSG, name: name))
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for `specify` with `is_expected` and one-liner expectations.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# specify { is_expected.to be_truthy }
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# it { is_expected.to be_truthy }
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# specify do
|
17
|
+
# # ...
|
18
|
+
# end
|
19
|
+
# specify { expect(sqrt(4)).to eq(2) }
|
20
|
+
#
|
21
|
+
class IsExpectedSpecify < Base
|
22
|
+
extend AutoCorrector
|
23
|
+
|
24
|
+
RESTRICT_ON_SEND = %i[specify].freeze
|
25
|
+
IS_EXPECTED_METHODS = ::Set[:is_expected, :are_expected].freeze
|
26
|
+
MSG = 'Use `it` instead of `specify`.'
|
27
|
+
|
28
|
+
# @!method offense?(node)
|
29
|
+
def_node_matcher :offense?, <<~PATTERN
|
30
|
+
(block (send _ :specify) _ (send (send _ IS_EXPECTED_METHODS) ...))
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def on_send(node)
|
34
|
+
block_node = node.parent
|
35
|
+
return unless block_node&.single_line? && offense?(block_node)
|
36
|
+
|
37
|
+
selector = node.loc.selector
|
38
|
+
add_offense(selector) do |corrector|
|
39
|
+
corrector.replace(selector, 'it')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -48,12 +48,17 @@ module RuboCop
|
|
48
48
|
# end
|
49
49
|
# end
|
50
50
|
#
|
51
|
-
# @example
|
52
|
-
# #
|
53
|
-
#
|
54
|
-
#
|
51
|
+
# @example `Max: 1` (default)
|
52
|
+
# # bad
|
53
|
+
# describe UserCreator do
|
54
|
+
# it 'builds a user' do
|
55
|
+
# expect(user.name).to eq("John")
|
56
|
+
# expect(user.age).to eq(22)
|
57
|
+
# end
|
58
|
+
# end
|
55
59
|
#
|
56
|
-
#
|
60
|
+
# @example `Max: 2`
|
61
|
+
# # good
|
57
62
|
# describe UserCreator do
|
58
63
|
# it 'builds a user' do
|
59
64
|
# expect(user.name).to eq("John")
|
@@ -6,20 +6,32 @@ module RuboCop
|
|
6
6
|
module Rails
|
7
7
|
# Checks that tests use `have_http_status` instead of equality matchers.
|
8
8
|
#
|
9
|
-
# @example
|
9
|
+
# @example ResponseMethods: ['response', 'last_response'] (default)
|
10
10
|
# # bad
|
11
11
|
# expect(response.status).to be(200)
|
12
|
-
# expect(
|
12
|
+
# expect(last_response.code).to eq("200")
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# expect(response).to have_http_status(200)
|
16
|
+
# expect(last_response).to have_http_status(200)
|
17
|
+
#
|
18
|
+
# @example ResponseMethods: ['foo_response']
|
19
|
+
# # bad
|
20
|
+
# expect(foo_response.status).to be(200)
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# expect(foo_response).to have_http_status(200)
|
24
|
+
#
|
25
|
+
# # also good
|
26
|
+
# expect(response).to have_http_status(200)
|
27
|
+
# expect(last_response).to have_http_status(200)
|
16
28
|
#
|
17
29
|
class HaveHttpStatus < ::RuboCop::Cop::Base
|
18
30
|
extend AutoCorrector
|
19
31
|
|
20
32
|
MSG =
|
21
|
-
'Prefer `expect(response).%<to>s
|
22
|
-
'over `%<bad_code>s`.'
|
33
|
+
'Prefer `expect(%<response>s).%<to>s ' \
|
34
|
+
'have_http_status(%<status>s)` over `%<bad_code>s`.'
|
23
35
|
|
24
36
|
RUNNERS = %i[to to_not not_to].to_set
|
25
37
|
RESTRICT_ON_SEND = RUNNERS
|
@@ -28,26 +40,38 @@ module RuboCop
|
|
28
40
|
def_node_matcher :match_status, <<~PATTERN
|
29
41
|
(send
|
30
42
|
(send nil? :expect
|
31
|
-
$(send (send nil?
|
43
|
+
$(send $(send nil? #response_methods?) {:status :code})
|
32
44
|
)
|
33
45
|
$RUNNERS
|
34
46
|
$(send nil? {:be :eq :eql :equal} ({int str} $_))
|
35
47
|
)
|
36
48
|
PATTERN
|
37
49
|
|
38
|
-
def on_send(node)
|
39
|
-
match_status(node) do
|
50
|
+
def on_send(node) # rubocop:disable Metrics/MethodLength
|
51
|
+
match_status(node) do
|
52
|
+
|response_status, response_method, to, match, status|
|
40
53
|
return unless status.to_s.match?(/\A\d+\z/)
|
41
54
|
|
42
|
-
message = format(MSG,
|
55
|
+
message = format(MSG, response: response_method.method_name,
|
56
|
+
to: to, status: status,
|
43
57
|
bad_code: node.source)
|
44
58
|
add_offense(node, message: message) do |corrector|
|
45
|
-
corrector.replace(response_status,
|
59
|
+
corrector.replace(response_status, response_method.method_name)
|
46
60
|
corrector.replace(match.loc.selector, 'have_http_status')
|
47
61
|
corrector.replace(match.first_argument, status.to_s)
|
48
62
|
end
|
49
63
|
end
|
50
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def response_methods?(name)
|
69
|
+
response_methods.include?(name.to_s)
|
70
|
+
end
|
71
|
+
|
72
|
+
def response_methods
|
73
|
+
cop_config.fetch('ResponseMethods', [])
|
74
|
+
end
|
51
75
|
end
|
52
76
|
end
|
53
77
|
end
|
@@ -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
|
@@ -9,10 +9,12 @@ module RuboCop
|
|
9
9
|
# # bad
|
10
10
|
# expect(foo).to be_exist(bar)
|
11
11
|
# expect(foo).not_to be_include(bar)
|
12
|
+
# expect(foo).to be_all(bar)
|
12
13
|
#
|
13
14
|
# # good
|
14
15
|
# expect(foo).to exist(bar)
|
15
16
|
# expect(foo).not_to include(bar)
|
17
|
+
# expect(foo).to all be(bar)
|
16
18
|
#
|
17
19
|
class RedundantPredicateMatcher < Base
|
18
20
|
extend AutoCorrector
|
@@ -25,7 +27,7 @@ module RuboCop
|
|
25
27
|
|
26
28
|
def on_send(node)
|
27
29
|
return if node.parent.block_type? || node.arguments.empty?
|
28
|
-
return unless
|
30
|
+
return unless replaceable_arguments?(node)
|
29
31
|
|
30
32
|
method_name = node.method_name.to_s
|
31
33
|
replaced = replaced_method_name(method_name)
|
@@ -43,7 +45,7 @@ module RuboCop
|
|
43
45
|
format(MSG, bad: bad_method, good: good_method)
|
44
46
|
end
|
45
47
|
|
46
|
-
def
|
48
|
+
def replaceable_arguments?(node)
|
47
49
|
if node.method?(:be_all)
|
48
50
|
node.first_argument.send_type?
|
49
51
|
else
|
@@ -0,0 +1,120 @@
|
|
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
|
+
class RepeatedSubjectCall < Base
|
27
|
+
include TopLevelGroup
|
28
|
+
|
29
|
+
MSG = 'Calls to subject are memoized, this block is misleading'
|
30
|
+
|
31
|
+
# @!method subject?(node)
|
32
|
+
# Find a named or unnamed subject definition
|
33
|
+
#
|
34
|
+
# @example anonymous subject
|
35
|
+
# subject?(parse('subject { foo }').ast) do |name|
|
36
|
+
# name # => :subject
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @example named subject
|
40
|
+
# subject?(parse('subject(:thing) { foo }').ast) do |name|
|
41
|
+
# name # => :thing
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param node [RuboCop::AST::Node]
|
45
|
+
#
|
46
|
+
# @yield [Symbol] subject name
|
47
|
+
def_node_matcher :subject?, <<-PATTERN
|
48
|
+
(block
|
49
|
+
(send nil?
|
50
|
+
{ #Subjects.all (sym $_) | $#Subjects.all }
|
51
|
+
) args ...)
|
52
|
+
PATTERN
|
53
|
+
|
54
|
+
# @!method subject_calls(node, method_name)
|
55
|
+
def_node_search :subject_calls, <<~PATTERN
|
56
|
+
(send nil? %)
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def on_top_level_group(node)
|
60
|
+
@subjects_by_node = detect_subjects_in_scope(node)
|
61
|
+
|
62
|
+
detect_offenses_in_block(node)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def detect_offense(example_node, subject_node)
|
68
|
+
walker = subject_node
|
69
|
+
|
70
|
+
while walker.parent? && walker.parent != example_node.body
|
71
|
+
walker = walker.parent
|
72
|
+
|
73
|
+
if walker.block_type? && walker.method?(:expect)
|
74
|
+
add_offense(walker)
|
75
|
+
return
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def detect_offenses_in_block(node, subject_names = [])
|
81
|
+
subject_names = [*subject_names, *@subjects_by_node[node]]
|
82
|
+
|
83
|
+
if example?(node)
|
84
|
+
return detect_offenses_in_example(node, subject_names)
|
85
|
+
end
|
86
|
+
|
87
|
+
node.each_child_node(:send, :def, :block, :begin) do |child|
|
88
|
+
detect_offenses_in_block(child, subject_names)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def detect_offenses_in_example(node, subject_names)
|
93
|
+
return unless node.body
|
94
|
+
|
95
|
+
subjects_used = Hash.new(false)
|
96
|
+
|
97
|
+
subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
|
98
|
+
if subjects_used[call.method_name]
|
99
|
+
detect_offense(node, call)
|
100
|
+
else
|
101
|
+
subjects_used[call.method_name] = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def detect_subjects_in_scope(node)
|
107
|
+
node.each_descendant(:block).with_object({}) do |child, h|
|
108
|
+
subject?(child) do |name|
|
109
|
+
outer_example_group = child.each_ancestor(:block).find do |a|
|
110
|
+
example_group?(a)
|
111
|
+
end
|
112
|
+
|
113
|
+
(h[outer_example_group] ||= []) << name
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -46,14 +46,13 @@ module RuboCop
|
|
46
46
|
# @!method shared_examples(node)
|
47
47
|
def_node_matcher :shared_examples, <<~PATTERN
|
48
48
|
{
|
49
|
-
(send #rspec? #SharedGroups.all ...)
|
50
|
-
(send nil? #Includes.all ...)
|
49
|
+
(send #rspec? #SharedGroups.all $_ ...)
|
50
|
+
(send nil? #Includes.all $_ ...)
|
51
51
|
}
|
52
52
|
PATTERN
|
53
53
|
|
54
54
|
def on_send(node)
|
55
|
-
shared_examples(node) do
|
56
|
-
ast_node = node.first_argument
|
55
|
+
shared_examples(node) do |ast_node|
|
57
56
|
next unless offense?(ast_node)
|
58
57
|
|
59
58
|
checker = new_checker(ast_node)
|
@@ -78,6 +78,7 @@ require_relative 'rspec/implicit_subject'
|
|
78
78
|
require_relative 'rspec/indexed_let'
|
79
79
|
require_relative 'rspec/instance_spy'
|
80
80
|
require_relative 'rspec/instance_variable'
|
81
|
+
require_relative 'rspec/is_expected_specify'
|
81
82
|
require_relative 'rspec/it_behaves_like'
|
82
83
|
require_relative 'rspec/iterated_expectation'
|
83
84
|
require_relative 'rspec/leading_subject'
|
@@ -113,6 +114,7 @@ require_relative 'rspec/repeated_example'
|
|
113
114
|
require_relative 'rspec/repeated_example_group_body'
|
114
115
|
require_relative 'rspec/repeated_example_group_description'
|
115
116
|
require_relative 'rspec/repeated_include_example'
|
117
|
+
require_relative 'rspec/repeated_subject_call'
|
116
118
|
require_relative 'rspec/return_from_stub'
|
117
119
|
require_relative 'rspec/scattered_let'
|
118
120
|
require_relative 'rspec/scattered_setup'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.27.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Backus
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-02-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rubocop
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- lib/rubocop/cop/rspec/indexed_let.rb
|
140
140
|
- lib/rubocop/cop/rspec/instance_spy.rb
|
141
141
|
- lib/rubocop/cop/rspec/instance_variable.rb
|
142
|
+
- lib/rubocop/cop/rspec/is_expected_specify.rb
|
142
143
|
- lib/rubocop/cop/rspec/it_behaves_like.rb
|
143
144
|
- lib/rubocop/cop/rspec/iterated_expectation.rb
|
144
145
|
- lib/rubocop/cop/rspec/leading_subject.rb
|
@@ -192,6 +193,7 @@ files:
|
|
192
193
|
- lib/rubocop/cop/rspec/repeated_example_group_body.rb
|
193
194
|
- lib/rubocop/cop/rspec/repeated_example_group_description.rb
|
194
195
|
- lib/rubocop/cop/rspec/repeated_include_example.rb
|
196
|
+
- lib/rubocop/cop/rspec/repeated_subject_call.rb
|
195
197
|
- lib/rubocop/cop/rspec/return_from_stub.rb
|
196
198
|
- lib/rubocop/cop/rspec/scattered_let.rb
|
197
199
|
- lib/rubocop/cop/rspec/scattered_setup.rb
|
@@ -251,7 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
253
|
- !ruby/object:Gem::Version
|
252
254
|
version: '0'
|
253
255
|
requirements: []
|
254
|
-
rubygems_version: 3.
|
256
|
+
rubygems_version: 3.5.3
|
255
257
|
signing_key:
|
256
258
|
specification_version: 4
|
257
259
|
summary: Code style checking for RSpec files
|