rubocop-rspec_rails 2.31.0 → 2.33.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 +28 -0
- data/config/default.yml +22 -3
- data/lib/rubocop/cop/rspec_rails/avoid_setup_hook.rb +6 -3
- data/lib/rubocop/cop/rspec_rails/file_fixture.rb +45 -0
- data/lib/rubocop/cop/rspec_rails/have_http_status.rb +7 -3
- data/lib/rubocop/cop/rspec_rails/http_status.rb +48 -33
- data/lib/rubocop/cop/rspec_rails/http_status_name_consistency.rb +60 -0
- data/lib/rubocop/cop/rspec_rails/inferred_spec_type.rb +3 -8
- data/lib/rubocop/cop/rspec_rails/minitest_assertions.rb +106 -128
- data/lib/rubocop/cop/rspec_rails/negation_be_valid.rb +4 -4
- data/lib/rubocop/cop/rspec_rails/receive_perform_later.rb +107 -0
- data/lib/rubocop/cop/rspec_rails/travel_around.rb +44 -15
- data/lib/rubocop/cop/rspec_rails.rb +22 -0
- data/lib/rubocop/cop/rspec_rails_cops.rb +4 -7
- data/lib/rubocop/rspec_rails/version.rb +1 -1
- data/lib/rubocop-rspec_rails.rb +1 -1
- metadata +9 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a42fb50c1cc6fac2f7c5a5c81dcf9573d4cbc27390157cc0063b907c880b12ae
|
|
4
|
+
data.tar.gz: b4e95f46c045360b80c76177b975ba2932c247974e63d56cd7e376b176831288
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 594c53b8d30e097e94063476fd9d42d014ae3f4913a029956b66a9c25be1064d8136bedeea8247e7845bfd6278b614a8a7ea7da2a71e1677739500c44ce24782
|
|
7
|
+
data.tar.gz: 7318471ee0e4062142c4a64ca3ad5a718d7d00c6653d6210d7345bbf60208fe51f8b4c815849e369daecf3810d59d351c69b330ddb7130c028b89cbeb21af152
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## Master (Unreleased)
|
|
4
4
|
|
|
5
|
+
## 2.33.0 (2026-10-07)
|
|
6
|
+
|
|
7
|
+
- Add `RSpecRails/FileFixture` cop to prefer `file_fixture` over hardcoded fixture paths. ([@ydah])
|
|
8
|
+
- Fix `RSpecRails/HttpStatus` to detect unknown symbolic HTTP statuses and handle custom numeric string statuses consistently. ([@ydah])
|
|
9
|
+
- Speed up loading rubocop-rspec_rails by lazily loading only the cops needed for a run. This requires RuboCop 1.89.0+. ([@koic])
|
|
10
|
+
- Fix offense message for `RSpecRails/HttpStatusNameConsistency` cop. ([@fatkodima])
|
|
11
|
+
- Fix `RSpecRails/MinitestAssertions` autocorrection for quoted predicate symbols. ([@ydah])
|
|
12
|
+
- Fix `RSpecRails/AvoidSetupHook` to only check setup hooks inside example groups. ([@ydah])
|
|
13
|
+
- Fix a false positive for `RspecRails/NegationBeValid` when use `to_not`. ([@ydah])
|
|
14
|
+
- Fix false positives for `RSpecRails/TravelAround` when travel does not directly wrap the around example. ([@ydah])
|
|
15
|
+
- Supporting correcting `assest_redirected_to` in `RSpec/Rails/MinitestAssertions`. ([@nzlaura])
|
|
16
|
+
- Add new `RSpecRails/ReceivePerformLater` cop. ([@ydah])
|
|
17
|
+
- Fix `RSpecRails/ReceivePerformLater` offense range to exclude chains after the expectation. ([@ydah])
|
|
18
|
+
- Fix a false positive for `RSpecRails/ReceivePerformLater` when `receive(:perform_later)` is used inside another matcher. ([@ydah])
|
|
19
|
+
- Support `described_class` for `RSpecRails/ReceivePerformLater`. ([@ydah])
|
|
20
|
+
- Fix `RSpecRails/TravelAround` autocorrection to keep the indentation of the surrounding `around` block. ([@eugeneius])
|
|
21
|
+
- Fix `RSpecRails/ReceivePerformLater` offense messages for negative expectations. ([@ydah])
|
|
22
|
+
|
|
23
|
+
## 2.32.0 (2025-11-12)
|
|
24
|
+
|
|
25
|
+
- Add `RSpecRails/HttpStatusNameConsistency` cop. ([@taketo1113])
|
|
26
|
+
- Support correcting `assert_response` assertion in in `RSpec/Rails/MinitestAssertions`. ([@nzlaura])
|
|
27
|
+
|
|
5
28
|
## 2.31.0 (2025-03-10)
|
|
6
29
|
|
|
7
30
|
- Handle unknown HTTP status codes for `RSpecRails/HttpStatus` cop. ([@viralpraxis])
|
|
@@ -78,14 +101,19 @@
|
|
|
78
101
|
[@anthony-robin]: https://github.com/anthony-robin
|
|
79
102
|
[@bquorning]: https://github.com/bquorning
|
|
80
103
|
[@corydiamand]: https://github.com/corydiamand
|
|
104
|
+
[@eugeneius]: https://github.com/eugeneius
|
|
105
|
+
[@fatkodima]: https://github.com/fatkodima
|
|
81
106
|
[@g-rath]: https://github.com/G-Rath
|
|
82
107
|
[@jojos003]: https://github.com/jojos003
|
|
108
|
+
[@koic]: https://github.com/koic
|
|
83
109
|
[@mothonmars]: https://github.com/MothOnMars
|
|
84
110
|
[@mvz]: https://github.com/mvz
|
|
111
|
+
[@nzlaura]: https://github.com/nzlaura
|
|
85
112
|
[@paydaylight]: https://github.com/paydaylight
|
|
86
113
|
[@pirj]: https://github.com/pirj
|
|
87
114
|
[@r7kamura]: https://github.com/r7kamura
|
|
88
115
|
[@splattael]: https://github.com/splattael
|
|
116
|
+
[@taketo1113]: https://github.com/taketo1113
|
|
89
117
|
[@tmaier]: https://github.com/tmaier
|
|
90
118
|
[@viralpraxis]: https://github.com/viralpraxis
|
|
91
119
|
[@ydah]: https://github.com/ydah
|
data/config/default.yml
CHANGED
|
@@ -7,11 +7,18 @@ RSpecRails:
|
|
|
7
7
|
- "**/spec/**/*"
|
|
8
8
|
|
|
9
9
|
RSpecRails/AvoidSetupHook:
|
|
10
|
-
Description: Checks that tests use RSpec `before`
|
|
10
|
+
Description: Checks that tests use RSpec `before` instead of Rails `setup`.
|
|
11
11
|
Enabled: pending
|
|
12
12
|
VersionAdded: '2.4'
|
|
13
13
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/AvoidSetupHook
|
|
14
14
|
|
|
15
|
+
RSpecRails/FileFixture:
|
|
16
|
+
Description: Prefer `file_fixture` over hardcoded paths into `spec/fixtures`.
|
|
17
|
+
Enabled: pending
|
|
18
|
+
Safe: false
|
|
19
|
+
VersionAdded: '2.33'
|
|
20
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/FileFixture
|
|
21
|
+
|
|
15
22
|
RSpecRails/HaveHttpStatus:
|
|
16
23
|
Description: Checks that tests use `have_http_status` instead of equality matchers.
|
|
17
24
|
Enabled: pending
|
|
@@ -35,6 +42,12 @@ RSpecRails/HttpStatus:
|
|
|
35
42
|
VersionChanged: '2.20'
|
|
36
43
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatus
|
|
37
44
|
|
|
45
|
+
RSpecRails/HttpStatusNameConsistency:
|
|
46
|
+
Description: Enforces consistency by using the current HTTP status names.
|
|
47
|
+
Enabled: pending
|
|
48
|
+
VersionAdded: '2.32'
|
|
49
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/HttpStatusNameConsistency
|
|
50
|
+
|
|
38
51
|
RSpecRails/InferredSpecType:
|
|
39
52
|
Description: Identifies redundant spec type.
|
|
40
53
|
Enabled: pending
|
|
@@ -59,7 +72,7 @@ RSpecRails/InferredSpecType:
|
|
|
59
72
|
views: view
|
|
60
73
|
|
|
61
74
|
RSpecRails/MinitestAssertions:
|
|
62
|
-
Description:
|
|
75
|
+
Description: Detects Minitest-like matchers.
|
|
63
76
|
Enabled: pending
|
|
64
77
|
VersionAdded: '2.17'
|
|
65
78
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/MinitestAssertions
|
|
@@ -77,8 +90,14 @@ RSpecRails/NegationBeValid:
|
|
|
77
90
|
VersionChanged: '2.29'
|
|
78
91
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/NegationBeValid
|
|
79
92
|
|
|
93
|
+
RSpecRails/ReceivePerformLater:
|
|
94
|
+
Description: Prefer `have_enqueued_job` over `receive(:perform_later)`.
|
|
95
|
+
Enabled: pending
|
|
96
|
+
VersionAdded: '2.33'
|
|
97
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/ReceivePerformLater
|
|
98
|
+
|
|
80
99
|
RSpecRails/TravelAround:
|
|
81
|
-
Description:
|
|
100
|
+
Description: Checks for time travel helpers in `around` hooks.
|
|
82
101
|
Enabled: pending
|
|
83
102
|
Safe: false
|
|
84
103
|
VersionAdded: '2.19'
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module RSpecRails
|
|
6
|
-
# Checks that tests use RSpec `before`
|
|
6
|
+
# Checks that tests use RSpec `before` instead of Rails `setup`.
|
|
7
7
|
#
|
|
8
8
|
# @example
|
|
9
9
|
# # bad
|
|
@@ -16,8 +16,9 @@ module RuboCop
|
|
|
16
16
|
# allow(foo).to receive(:bar)
|
|
17
17
|
# end
|
|
18
18
|
#
|
|
19
|
-
class AvoidSetupHook <
|
|
19
|
+
class AvoidSetupHook < RuboCop::Cop::RSpec::Base
|
|
20
20
|
extend AutoCorrector
|
|
21
|
+
include RuboCop::Cop::RSpec::InsideExampleGroup
|
|
21
22
|
|
|
22
23
|
MSG = 'Use `before` instead of `setup`.'
|
|
23
24
|
|
|
@@ -28,7 +29,9 @@ module RuboCop
|
|
|
28
29
|
(args) _)
|
|
29
30
|
PATTERN
|
|
30
31
|
|
|
31
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
32
|
+
def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler, InternalAffairs/NumblockHandler
|
|
33
|
+
return unless inside_example_group?(node)
|
|
34
|
+
|
|
32
35
|
setup_call(node) do |setup|
|
|
33
36
|
add_offense(node) do |corrector|
|
|
34
37
|
corrector.replace setup, 'before'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpecRails
|
|
6
|
+
# Prefer `file_fixture` over hardcoded paths into `spec/fixtures`.
|
|
7
|
+
#
|
|
8
|
+
# @safety
|
|
9
|
+
# `file_fixture` uses the configured `file_fixture_path`, which defaults
|
|
10
|
+
# to `spec/fixtures/files`. A file under `spec/fixtures` may need to be
|
|
11
|
+
# moved or `file_fixture_path` configured before replacing the path.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # bad
|
|
15
|
+
# Rails.root.join('spec', 'fixtures', 'files', 'example.pdf')
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# file_fixture('example.pdf')
|
|
19
|
+
class FileFixture < RuboCop::Cop::Base
|
|
20
|
+
MSG = 'Prefer `file_fixture` for files under `spec/fixtures`.'
|
|
21
|
+
RESTRICT_ON_SEND = %i[join].freeze
|
|
22
|
+
|
|
23
|
+
# @!method fixture_path?(node)
|
|
24
|
+
def_node_matcher :fixture_path?, <<~PATTERN
|
|
25
|
+
(send
|
|
26
|
+
(send (const {nil? cbase} :Rails) :root)
|
|
27
|
+
:join (str "spec") (str "fixtures") _ ...)
|
|
28
|
+
PATTERN
|
|
29
|
+
|
|
30
|
+
# @!method combined_fixture_path?(node)
|
|
31
|
+
def_node_matcher :combined_fixture_path?, <<~PATTERN
|
|
32
|
+
(send
|
|
33
|
+
(send (const {nil? cbase} :Rails) :root)
|
|
34
|
+
:join (str "spec/fixtures") _ ...)
|
|
35
|
+
PATTERN
|
|
36
|
+
|
|
37
|
+
def on_send(node)
|
|
38
|
+
return unless fixture_path?(node) || combined_fixture_path?(node)
|
|
39
|
+
|
|
40
|
+
add_offense(node.loc.selector)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -5,7 +5,11 @@ module RuboCop
|
|
|
5
5
|
module RSpecRails
|
|
6
6
|
# Checks that tests use `have_http_status` instead of equality matchers.
|
|
7
7
|
#
|
|
8
|
-
# @
|
|
8
|
+
# @safety
|
|
9
|
+
# The autocorrection is marked as unsafe because
|
|
10
|
+
# `response.status` response is not always an HTTP response status.
|
|
11
|
+
#
|
|
12
|
+
# @example `ResponseMethods: ['response', 'last_response']` (default)
|
|
9
13
|
# # bad
|
|
10
14
|
# expect(response.status).to be(200)
|
|
11
15
|
# expect(last_response.code).to eq("200")
|
|
@@ -14,7 +18,7 @@ module RuboCop
|
|
|
14
18
|
# expect(response).to have_http_status(200)
|
|
15
19
|
# expect(last_response).to have_http_status(200)
|
|
16
20
|
#
|
|
17
|
-
# @example ResponseMethods: ['foo_response']
|
|
21
|
+
# @example `ResponseMethods: ['foo_response']`
|
|
18
22
|
# # bad
|
|
19
23
|
# expect(foo_response.status).to be(200)
|
|
20
24
|
#
|
|
@@ -25,7 +29,7 @@ module RuboCop
|
|
|
25
29
|
# expect(response).to have_http_status(200)
|
|
26
30
|
# expect(last_response).to have_http_status(200)
|
|
27
31
|
#
|
|
28
|
-
class HaveHttpStatus <
|
|
32
|
+
class HaveHttpStatus < RuboCop::Cop::Base
|
|
29
33
|
extend AutoCorrector
|
|
30
34
|
|
|
31
35
|
MSG =
|
|
@@ -64,9 +64,10 @@ module RuboCop
|
|
|
64
64
|
#
|
|
65
65
|
# # good
|
|
66
66
|
# it { is_expected.to have_http_status :ok }
|
|
67
|
-
class HttpStatus <
|
|
67
|
+
class HttpStatus < RuboCop::Cop::Base
|
|
68
68
|
extend AutoCorrector
|
|
69
69
|
include ConfigurableEnforcedStyle
|
|
70
|
+
|
|
70
71
|
RESTRICT_ON_SEND = %i[have_http_status].freeze
|
|
71
72
|
|
|
72
73
|
# @!method http_status(node)
|
|
@@ -142,16 +143,44 @@ module RuboCop
|
|
|
142
143
|
node.sym_type? && ALLOWED_STATUSES.include?(node.value)
|
|
143
144
|
end
|
|
144
145
|
|
|
146
|
+
def known_status_symbol?
|
|
147
|
+
node.sym_type? &&
|
|
148
|
+
::Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(node.value)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def known_status_code?
|
|
152
|
+
numeric_status_code? &&
|
|
153
|
+
::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(status_code_number)
|
|
154
|
+
end
|
|
155
|
+
|
|
145
156
|
def custom_http_status_code?
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
numeric_status_code? && !known_status_code?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def numeric_status_code?
|
|
161
|
+
node.int_type? || numeric_string?
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def numeric_string?
|
|
165
|
+
node.str_type? && node.value.match?(/\A\d+\z/)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def status_code_number
|
|
169
|
+
node.value.to_i
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def status_code_symbol
|
|
173
|
+
::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(status_code_number)
|
|
148
174
|
end
|
|
149
175
|
end
|
|
150
176
|
|
|
151
177
|
# :nodoc:
|
|
152
178
|
class SymbolicStyleChecker < StyleCheckerBase
|
|
153
179
|
def offensive?
|
|
154
|
-
|
|
180
|
+
return false if allowed_symbol? || known_status_symbol?
|
|
181
|
+
return false if custom_http_status_code?
|
|
182
|
+
|
|
183
|
+
true
|
|
155
184
|
end
|
|
156
185
|
|
|
157
186
|
def autocorrectable?
|
|
@@ -165,18 +194,17 @@ module RuboCop
|
|
|
165
194
|
private
|
|
166
195
|
|
|
167
196
|
def symbol
|
|
168
|
-
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def number
|
|
172
|
-
node.value.to_i
|
|
197
|
+
status_code_symbol if known_status_code?
|
|
173
198
|
end
|
|
174
199
|
end
|
|
175
200
|
|
|
176
201
|
# :nodoc:
|
|
177
202
|
class NumericStyleChecker < StyleCheckerBase
|
|
178
203
|
def offensive?
|
|
179
|
-
|
|
204
|
+
return false if node.int_type? || allowed_symbol?
|
|
205
|
+
return false if custom_http_status_code?
|
|
206
|
+
|
|
207
|
+
true
|
|
180
208
|
end
|
|
181
209
|
|
|
182
210
|
def autocorrectable?
|
|
@@ -189,20 +217,19 @@ module RuboCop
|
|
|
189
217
|
|
|
190
218
|
private
|
|
191
219
|
|
|
192
|
-
def symbol
|
|
193
|
-
node.value
|
|
194
|
-
end
|
|
195
|
-
|
|
196
220
|
def number
|
|
197
|
-
|
|
221
|
+
return status_code_number if known_status_code?
|
|
222
|
+
|
|
223
|
+
::Rack::Utils::SYMBOL_TO_STATUS_CODE[node.value.to_sym]
|
|
198
224
|
end
|
|
199
225
|
end
|
|
200
226
|
|
|
201
227
|
# :nodoc:
|
|
202
228
|
class BeStatusStyleChecker < StyleCheckerBase
|
|
203
229
|
def offensive?
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
return false if allowed_symbol? || custom_http_status_code?
|
|
231
|
+
|
|
232
|
+
true
|
|
206
233
|
end
|
|
207
234
|
|
|
208
235
|
def autocorrectable?
|
|
@@ -220,30 +247,18 @@ module RuboCop
|
|
|
220
247
|
private
|
|
221
248
|
|
|
222
249
|
def status_code
|
|
223
|
-
if
|
|
250
|
+
if known_status_symbol?
|
|
224
251
|
node.value
|
|
225
|
-
elsif
|
|
226
|
-
|
|
252
|
+
elsif known_status_code?
|
|
253
|
+
status_code_symbol
|
|
227
254
|
else
|
|
228
255
|
normalize_str
|
|
229
256
|
end
|
|
230
257
|
end
|
|
231
258
|
|
|
232
|
-
def symbol
|
|
233
|
-
::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def number
|
|
237
|
-
node.value.to_i
|
|
238
|
-
end
|
|
239
|
-
|
|
240
259
|
def normalize_str
|
|
241
260
|
str = node.value.to_s
|
|
242
|
-
if
|
|
243
|
-
::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(str.to_i)
|
|
244
|
-
elsif ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(str.to_sym)
|
|
245
|
-
str
|
|
246
|
-
end
|
|
261
|
+
str if ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(str.to_sym)
|
|
247
262
|
end
|
|
248
263
|
end
|
|
249
264
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpecRails
|
|
6
|
+
# Enforces consistency by using the current HTTP status names.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
#
|
|
10
|
+
# # bad
|
|
11
|
+
# it { is_expected.to have_http_status :unprocessable_entity }
|
|
12
|
+
#
|
|
13
|
+
# # good
|
|
14
|
+
# it { is_expected.to have_http_status :unprocessable_content }
|
|
15
|
+
#
|
|
16
|
+
class HttpStatusNameConsistency < RuboCop::Cop::Base
|
|
17
|
+
extend AutoCorrector
|
|
18
|
+
|
|
19
|
+
requires_gem 'rack', '>= 3.1.0'
|
|
20
|
+
|
|
21
|
+
MSG = 'Prefer `:%<preferred>s` over `:%<current>s`.'
|
|
22
|
+
|
|
23
|
+
RESTRICT_ON_SEND = %i[have_http_status].freeze
|
|
24
|
+
|
|
25
|
+
PREFERRED_STATUSES = {
|
|
26
|
+
unprocessable_entity: :unprocessable_content,
|
|
27
|
+
payload_too_large: :content_too_large
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# @!method http_status(node)
|
|
31
|
+
def_node_matcher :http_status, <<~PATTERN
|
|
32
|
+
(send nil? :have_http_status ${sym})
|
|
33
|
+
PATTERN
|
|
34
|
+
|
|
35
|
+
def on_send(node)
|
|
36
|
+
http_status(node) do |arg|
|
|
37
|
+
check_status_name_consistency(arg)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
alias on_csend on_send
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def check_status_name_consistency(node)
|
|
45
|
+
return unless node.sym_type? && PREFERRED_STATUSES.key?(node.value)
|
|
46
|
+
|
|
47
|
+
current_status = node.value
|
|
48
|
+
preferred_status = PREFERRED_STATUSES[current_status]
|
|
49
|
+
|
|
50
|
+
message = format(MSG, current: current_status,
|
|
51
|
+
preferred: preferred_status)
|
|
52
|
+
|
|
53
|
+
add_offense(node, message: message) do |corrector|
|
|
54
|
+
corrector.replace(node, ":#{preferred_status}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -30,12 +30,7 @@ module RuboCop
|
|
|
30
30
|
# RSpec.describe User, type: :common do
|
|
31
31
|
# end
|
|
32
32
|
#
|
|
33
|
-
# @example `Inferences`
|
|
34
|
-
# # .rubocop.yml
|
|
35
|
-
# # RSpecRails/InferredSpecType:
|
|
36
|
-
# # Inferences:
|
|
37
|
-
# # services: service
|
|
38
|
-
#
|
|
33
|
+
# @example `Inferences: {services: service}`
|
|
39
34
|
# # bad
|
|
40
35
|
# # spec/services/user_spec.rb
|
|
41
36
|
# RSpec.describe User, type: :service do
|
|
@@ -50,13 +45,13 @@ module RuboCop
|
|
|
50
45
|
# # spec/services/user_spec.rb
|
|
51
46
|
# RSpec.describe User, type: :common do
|
|
52
47
|
# end
|
|
53
|
-
class InferredSpecType <
|
|
48
|
+
class InferredSpecType < RuboCop::Cop::RSpec::Base
|
|
54
49
|
extend AutoCorrector
|
|
55
50
|
|
|
56
51
|
MSG = 'Remove redundant spec type.'
|
|
57
52
|
|
|
58
53
|
# @param [RuboCop::AST::BlockNode] node
|
|
59
|
-
def on_block(node)
|
|
54
|
+
def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler
|
|
60
55
|
return unless example_group?(node)
|
|
61
56
|
|
|
62
57
|
pair_node = describe_with_type(node)
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module RSpecRails
|
|
6
|
-
#
|
|
6
|
+
# Detects Minitest-like matchers.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
# starting with `assert_` or `refute_
|
|
8
|
+
# Detects the use of Minitest-like matchers
|
|
9
|
+
# (those starting with `assert_` or `refute_`).
|
|
10
10
|
#
|
|
11
11
|
# @example
|
|
12
12
|
# # bad
|
|
@@ -18,6 +18,8 @@ module RuboCop
|
|
|
18
18
|
# refute_empty(b)
|
|
19
19
|
# assert_true(a)
|
|
20
20
|
# assert_false(a)
|
|
21
|
+
# assert_response :ok
|
|
22
|
+
# assert_redirected_to '/users'
|
|
21
23
|
#
|
|
22
24
|
# # good
|
|
23
25
|
# expect(b).to eq(a)
|
|
@@ -28,8 +30,10 @@ module RuboCop
|
|
|
28
30
|
# expect(a).not_to be_empty
|
|
29
31
|
# expect(a).to be(true)
|
|
30
32
|
# expect(a).to be(false)
|
|
33
|
+
# expect(response).to have_http_status(:ok)
|
|
34
|
+
# expect(response).to redirect_to('/users')
|
|
31
35
|
#
|
|
32
|
-
class MinitestAssertions <
|
|
36
|
+
class MinitestAssertions < RuboCop::Cop::Base
|
|
33
37
|
extend AutoCorrector
|
|
34
38
|
|
|
35
39
|
# :nodoc:
|
|
@@ -38,12 +42,25 @@ module RuboCop
|
|
|
38
42
|
|
|
39
43
|
attr_reader :expected, :actual, :failure_message
|
|
40
44
|
|
|
45
|
+
def self.match_assertions(*matchers, captures:)
|
|
46
|
+
const_set(:MATCHERS, matchers.freeze)
|
|
47
|
+
|
|
48
|
+
matcher_pattern = matchers.map { |matcher| ":#{matcher}" }.join(' ')
|
|
49
|
+
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
50
|
+
(send nil? {#{matcher_pattern}} #{captures})
|
|
51
|
+
PATTERN
|
|
52
|
+
end
|
|
53
|
+
|
|
41
54
|
def self.minitest_assertion
|
|
42
55
|
# :nocov:
|
|
43
56
|
raise NotImplementedError
|
|
44
57
|
# :nocov:
|
|
45
58
|
end
|
|
46
59
|
|
|
60
|
+
def self.match(expected, actual, failure_message)
|
|
61
|
+
new(expected, actual, failure_message.first)
|
|
62
|
+
end
|
|
63
|
+
|
|
47
64
|
def initialize(expected, actual, failure_message)
|
|
48
65
|
@expected = expected&.source
|
|
49
66
|
@actual = actual.source
|
|
@@ -72,20 +89,10 @@ module RuboCop
|
|
|
72
89
|
|
|
73
90
|
# :nodoc:
|
|
74
91
|
class EqualAssertion < BasicAssertion
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
].freeze
|
|
80
|
-
|
|
81
|
-
# @!method self.minitest_assertion(node)
|
|
82
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
83
|
-
(send nil? {:assert_equal :assert_not_equal :refute_equal} $_ $_ $_?)
|
|
84
|
-
PATTERN
|
|
85
|
-
|
|
86
|
-
def self.match(expected, actual, failure_message)
|
|
87
|
-
new(expected, actual, failure_message.first)
|
|
88
|
-
end
|
|
92
|
+
match_assertions :assert_equal,
|
|
93
|
+
:assert_not_equal,
|
|
94
|
+
:refute_equal,
|
|
95
|
+
captures: '$_ $_ $_?'
|
|
89
96
|
|
|
90
97
|
def assertion
|
|
91
98
|
"eq(#{expected})"
|
|
@@ -94,20 +101,10 @@ module RuboCop
|
|
|
94
101
|
|
|
95
102
|
# :nodoc:
|
|
96
103
|
class KindOfAssertion < BasicAssertion
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
].freeze
|
|
102
|
-
|
|
103
|
-
# @!method self.minitest_assertion(node)
|
|
104
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
105
|
-
(send nil? {:assert_kind_of :assert_not_kind_of :refute_kind_of} $_ $_ $_?)
|
|
106
|
-
PATTERN
|
|
107
|
-
|
|
108
|
-
def self.match(expected, actual, failure_message)
|
|
109
|
-
new(expected, actual, failure_message.first)
|
|
110
|
-
end
|
|
104
|
+
match_assertions :assert_kind_of,
|
|
105
|
+
:assert_not_kind_of,
|
|
106
|
+
:refute_kind_of,
|
|
107
|
+
captures: '$_ $_ $_?'
|
|
111
108
|
|
|
112
109
|
def assertion
|
|
113
110
|
"be_a_kind_of(#{expected})"
|
|
@@ -116,20 +113,10 @@ module RuboCop
|
|
|
116
113
|
|
|
117
114
|
# :nodoc:
|
|
118
115
|
class InstanceOfAssertion < BasicAssertion
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
].freeze
|
|
124
|
-
|
|
125
|
-
# @!method self.minitest_assertion(node)
|
|
126
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
127
|
-
(send nil? {:assert_instance_of :assert_not_instance_of :refute_instance_of} $_ $_ $_?)
|
|
128
|
-
PATTERN
|
|
129
|
-
|
|
130
|
-
def self.match(expected, actual, failure_message)
|
|
131
|
-
new(expected, actual, failure_message.first)
|
|
132
|
-
end
|
|
116
|
+
match_assertions :assert_instance_of,
|
|
117
|
+
:assert_not_instance_of,
|
|
118
|
+
:refute_instance_of,
|
|
119
|
+
captures: '$_ $_ $_?'
|
|
133
120
|
|
|
134
121
|
def assertion
|
|
135
122
|
"be_an_instance_of(#{expected})"
|
|
@@ -138,16 +125,10 @@ module RuboCop
|
|
|
138
125
|
|
|
139
126
|
# :nodoc:
|
|
140
127
|
class IncludesAssertion < BasicAssertion
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
].freeze
|
|
146
|
-
|
|
147
|
-
# @!method self.minitest_assertion(node)
|
|
148
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
149
|
-
(send nil? {:assert_includes :assert_not_includes :refute_includes} $_ $_ $_?)
|
|
150
|
-
PATTERN
|
|
128
|
+
match_assertions :assert_includes,
|
|
129
|
+
:assert_not_includes,
|
|
130
|
+
:refute_includes,
|
|
131
|
+
captures: '$_ $_ $_?'
|
|
151
132
|
|
|
152
133
|
def self.match(collection, expected, failure_message)
|
|
153
134
|
new(expected, collection, failure_message.first)
|
|
@@ -160,16 +141,10 @@ module RuboCop
|
|
|
160
141
|
|
|
161
142
|
# :nodoc:
|
|
162
143
|
class InDeltaAssertion < BasicAssertion
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
].freeze
|
|
168
|
-
|
|
169
|
-
# @!method self.minitest_assertion(node)
|
|
170
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
171
|
-
(send nil? {:assert_in_delta :assert_not_in_delta :refute_in_delta} $_ $_ $_? $_?)
|
|
172
|
-
PATTERN
|
|
144
|
+
match_assertions :assert_in_delta,
|
|
145
|
+
:assert_not_in_delta,
|
|
146
|
+
:refute_in_delta,
|
|
147
|
+
captures: '$_ $_ $_? $_?'
|
|
173
148
|
|
|
174
149
|
def self.match(expected, actual, delta, failure_message)
|
|
175
150
|
new(expected, actual, delta.first, failure_message.first)
|
|
@@ -188,43 +163,41 @@ module RuboCop
|
|
|
188
163
|
|
|
189
164
|
# :nodoc:
|
|
190
165
|
class PredicateAssertion < BasicAssertion
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
].freeze
|
|
196
|
-
|
|
197
|
-
# @!method self.minitest_assertion(node)
|
|
198
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
199
|
-
(send nil? {:assert_predicate :assert_not_predicate :refute_predicate} $_ ${sym} $_?)
|
|
200
|
-
PATTERN
|
|
166
|
+
match_assertions :assert_predicate,
|
|
167
|
+
:assert_not_predicate,
|
|
168
|
+
:refute_predicate,
|
|
169
|
+
captures: '$_ ${sym} $_?'
|
|
201
170
|
|
|
202
171
|
def self.match(subject, predicate, failure_message)
|
|
203
|
-
|
|
172
|
+
predicate_name = predicate.value.to_s
|
|
173
|
+
return nil unless predicate_name.end_with?('?')
|
|
174
|
+
|
|
175
|
+
matcher_name = predicate_name.delete_suffix('?')
|
|
176
|
+
return nil unless valid_matcher_name?(matcher_name)
|
|
204
177
|
|
|
205
|
-
new(predicate, subject, failure_message.first)
|
|
178
|
+
new(predicate, subject, failure_message.first, matcher_name)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def self.valid_matcher_name?(matcher_name)
|
|
182
|
+
matcher_name.match?(/\A\w+\z/)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def initialize(expected, actual, failure_message, matcher_name)
|
|
186
|
+
super(expected, actual, failure_message)
|
|
187
|
+
|
|
188
|
+
@matcher_name = matcher_name
|
|
206
189
|
end
|
|
207
190
|
|
|
208
191
|
def assertion
|
|
209
|
-
"be_#{
|
|
192
|
+
"be_#{@matcher_name}"
|
|
210
193
|
end
|
|
211
194
|
end
|
|
212
195
|
|
|
213
196
|
# :nodoc:
|
|
214
197
|
class MatchAssertion < BasicAssertion
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
].freeze
|
|
219
|
-
|
|
220
|
-
# @!method self.minitest_assertion(node)
|
|
221
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
222
|
-
(send nil? {:assert_match :refute_match} $_ $_ $_?)
|
|
223
|
-
PATTERN
|
|
224
|
-
|
|
225
|
-
def self.match(matcher, actual, failure_message)
|
|
226
|
-
new(matcher, actual, failure_message.first)
|
|
227
|
-
end
|
|
198
|
+
match_assertions :assert_match,
|
|
199
|
+
:refute_match,
|
|
200
|
+
captures: '$_ $_ $_?'
|
|
228
201
|
|
|
229
202
|
def assertion
|
|
230
203
|
"match(#{expected})"
|
|
@@ -233,16 +206,10 @@ module RuboCop
|
|
|
233
206
|
|
|
234
207
|
# :nodoc:
|
|
235
208
|
class NilAssertion < BasicAssertion
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
].freeze
|
|
241
|
-
|
|
242
|
-
# @!method self.minitest_assertion(node)
|
|
243
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
244
|
-
(send nil? {:assert_nil :assert_not_nil :refute_nil} $_ $_?)
|
|
245
|
-
PATTERN
|
|
209
|
+
match_assertions :assert_nil,
|
|
210
|
+
:assert_not_nil,
|
|
211
|
+
:refute_nil,
|
|
212
|
+
captures: '$_ $_?'
|
|
246
213
|
|
|
247
214
|
def self.match(actual, failure_message)
|
|
248
215
|
new(nil, actual, failure_message.first)
|
|
@@ -255,16 +222,10 @@ module RuboCop
|
|
|
255
222
|
|
|
256
223
|
# :nodoc:
|
|
257
224
|
class EmptyAssertion < BasicAssertion
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
].freeze
|
|
263
|
-
|
|
264
|
-
# @!method self.minitest_assertion(node)
|
|
265
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
266
|
-
(send nil? {:assert_empty :assert_not_empty :refute_empty} $_ $_?)
|
|
267
|
-
PATTERN
|
|
225
|
+
match_assertions :assert_empty,
|
|
226
|
+
:assert_not_empty,
|
|
227
|
+
:refute_empty,
|
|
228
|
+
captures: '$_ $_?'
|
|
268
229
|
|
|
269
230
|
def self.match(actual, failure_message)
|
|
270
231
|
new(nil, actual, failure_message.first)
|
|
@@ -277,14 +238,7 @@ module RuboCop
|
|
|
277
238
|
|
|
278
239
|
# :nodoc:
|
|
279
240
|
class TrueAssertion < BasicAssertion
|
|
280
|
-
|
|
281
|
-
assert_true
|
|
282
|
-
].freeze
|
|
283
|
-
|
|
284
|
-
# @!method self.minitest_assertion(node)
|
|
285
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
286
|
-
(send nil? {:assert_true} $_ $_?)
|
|
287
|
-
PATTERN
|
|
241
|
+
match_assertions :assert_true, captures: '$_ $_?'
|
|
288
242
|
|
|
289
243
|
def self.match(actual, failure_message)
|
|
290
244
|
new(nil, actual, failure_message.first)
|
|
@@ -297,14 +251,7 @@ module RuboCop
|
|
|
297
251
|
|
|
298
252
|
# :nodoc:
|
|
299
253
|
class FalseAssertion < BasicAssertion
|
|
300
|
-
|
|
301
|
-
assert_false
|
|
302
|
-
].freeze
|
|
303
|
-
|
|
304
|
-
# @!method self.minitest_assertion(node)
|
|
305
|
-
def_node_matcher 'self.minitest_assertion', <<~PATTERN # rubocop:disable InternalAffairs/NodeMatcherDirective
|
|
306
|
-
(send nil? {:assert_false} $_ $_?)
|
|
307
|
-
PATTERN
|
|
254
|
+
match_assertions :assert_false, captures: '$_ $_?'
|
|
308
255
|
|
|
309
256
|
def self.match(actual, failure_message)
|
|
310
257
|
new(nil, actual, failure_message.first)
|
|
@@ -315,6 +262,37 @@ module RuboCop
|
|
|
315
262
|
end
|
|
316
263
|
end
|
|
317
264
|
|
|
265
|
+
# :nodoc:
|
|
266
|
+
module ResponseAssertionActual
|
|
267
|
+
RESPONSE_NODE = Struct.new(:source).new('response')
|
|
268
|
+
|
|
269
|
+
def match(expected, failure_message)
|
|
270
|
+
new(expected, RESPONSE_NODE, failure_message.first)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# :nodoc:
|
|
275
|
+
class ResponseAssertion < BasicAssertion
|
|
276
|
+
extend ResponseAssertionActual
|
|
277
|
+
|
|
278
|
+
match_assertions :assert_response, captures: '$_ $_?'
|
|
279
|
+
|
|
280
|
+
def assertion
|
|
281
|
+
"have_http_status(#{expected})"
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# :nodoc:
|
|
286
|
+
class RedirectAssertion < BasicAssertion
|
|
287
|
+
extend ResponseAssertionActual
|
|
288
|
+
|
|
289
|
+
match_assertions :assert_redirected_to, captures: '$_ $_?'
|
|
290
|
+
|
|
291
|
+
def assertion
|
|
292
|
+
"redirect_to(#{expected})"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
318
296
|
MSG = 'Use `%<prefer>s`.'
|
|
319
297
|
|
|
320
298
|
# TODO: replace with `BasicAssertion.subclasses` in Ruby 3.1+
|
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
|
9
9
|
# This cop is unsafe because it cannot guarantee that
|
|
10
10
|
# the test target is an instance of `ActiveModel::Validations``.
|
|
11
11
|
#
|
|
12
|
-
# @example EnforcedStyle: not_to (default)
|
|
12
|
+
# @example `EnforcedStyle: not_to` (default)
|
|
13
13
|
# # bad
|
|
14
14
|
# expect(foo).to be_invalid
|
|
15
15
|
#
|
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
|
19
19
|
# # good (with method chain)
|
|
20
20
|
# expect(foo).to be_invalid.and be_odd
|
|
21
21
|
#
|
|
22
|
-
# @example EnforcedStyle: be_invalid
|
|
22
|
+
# @example `EnforcedStyle: be_invalid`
|
|
23
23
|
# # bad
|
|
24
24
|
# expect(foo).not_to be_valid
|
|
25
25
|
#
|
|
@@ -29,7 +29,7 @@ module RuboCop
|
|
|
29
29
|
# # good (with method chain)
|
|
30
30
|
# expect(foo).to be_invalid.or be_even
|
|
31
31
|
#
|
|
32
|
-
class NegationBeValid <
|
|
32
|
+
class NegationBeValid < RuboCop::Cop::Base
|
|
33
33
|
extend AutoCorrector
|
|
34
34
|
include ConfigurableEnforcedStyle
|
|
35
35
|
|
|
@@ -38,7 +38,7 @@ module RuboCop
|
|
|
38
38
|
|
|
39
39
|
# @!method not_to?(node)
|
|
40
40
|
def_node_matcher :not_to?, <<~PATTERN
|
|
41
|
-
(send ... :not_to (send nil? :be_valid ...))
|
|
41
|
+
(send ... {:not_to :to_not} (send nil? :be_valid ...))
|
|
42
42
|
PATTERN
|
|
43
43
|
|
|
44
44
|
# @!method be_invalid?(node)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpecRails
|
|
6
|
+
# Prefer `have_enqueued_job` over `receive(:perform_later)`.
|
|
7
|
+
#
|
|
8
|
+
# The `have_enqueued_job` matcher is preferred for testing ActiveJob
|
|
9
|
+
# enqueuing. It is more explicit and provides better clarity than
|
|
10
|
+
# using `receive(:perform_later)`.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# expect(MyJob).to receive(:perform_later)
|
|
15
|
+
# do_something
|
|
16
|
+
#
|
|
17
|
+
# # bad
|
|
18
|
+
# allow(MyJob).to receive(:perform_later)
|
|
19
|
+
# do_something
|
|
20
|
+
# expect(MyJob).to have_received(:perform_later)
|
|
21
|
+
#
|
|
22
|
+
# # bad
|
|
23
|
+
# expect(MyJob).to receive(:perform_later).with(user, order)
|
|
24
|
+
#
|
|
25
|
+
# # good
|
|
26
|
+
# expect { do_something }.to have_enqueued_job(MyJob)
|
|
27
|
+
#
|
|
28
|
+
# # good
|
|
29
|
+
# expect { do_something }.to have_enqueued_job(MyJob).with(user, order)
|
|
30
|
+
#
|
|
31
|
+
# # good
|
|
32
|
+
# expect { do_something }
|
|
33
|
+
# .to have_enqueued_job(MyJob)
|
|
34
|
+
# .on_queue('mailers')
|
|
35
|
+
# .at(Date.tomorrow.noon)
|
|
36
|
+
#
|
|
37
|
+
class ReceivePerformLater < RuboCop::Cop::Base
|
|
38
|
+
MSG = 'Prefer `expect { ... }.%<runner>s ' \
|
|
39
|
+
'have_enqueued_job(%<job_class>s)` ' \
|
|
40
|
+
'over `%<receiver>s(%<job_class>s).%<to>s ' \
|
|
41
|
+
'%<matcher>s(:perform_later)`.'
|
|
42
|
+
|
|
43
|
+
RESTRICT_ON_SEND = %i[receive have_received].to_set
|
|
44
|
+
RUNNERS = %i[to to_not not_to].freeze
|
|
45
|
+
|
|
46
|
+
# @!method receive_perform_later?(node)
|
|
47
|
+
def_node_matcher :receive_perform_later?, <<~PATTERN
|
|
48
|
+
(send nil? {:receive :have_received}
|
|
49
|
+
(sym :perform_later))
|
|
50
|
+
PATTERN
|
|
51
|
+
|
|
52
|
+
# @!method expect_or_allow?(node)
|
|
53
|
+
def_node_matcher :expect_or_allow?, <<~PATTERN
|
|
54
|
+
(send nil? {:expect :allow} {const_type? (send nil? :described_class)})
|
|
55
|
+
PATTERN
|
|
56
|
+
|
|
57
|
+
def on_send(node)
|
|
58
|
+
return unless receive_perform_later?(node)
|
|
59
|
+
return unless (runner_node = find_runner_node(node))
|
|
60
|
+
return unless runner_matcher?(node, runner_node)
|
|
61
|
+
|
|
62
|
+
register_offense(node, runner_node)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def register_offense(matcher_node, runner_node)
|
|
68
|
+
expect_node = runner_node.receiver
|
|
69
|
+
return unless expect_or_allow?(expect_node)
|
|
70
|
+
return if allow_receive_combination?(expect_node, matcher_node)
|
|
71
|
+
|
|
72
|
+
job_class = expect_node.first_argument
|
|
73
|
+
add_offense(runner_node,
|
|
74
|
+
message: offense_message(expect_node, job_class,
|
|
75
|
+
runner_node, matcher_node))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def allow_receive_combination?(expect_node, matcher_node)
|
|
79
|
+
expect_node.method?(:allow) && matcher_node.method?(:receive)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def offense_message(expect_node, job_class, runner_node, matcher_node)
|
|
83
|
+
format(MSG,
|
|
84
|
+
receiver: expect_node.method_name,
|
|
85
|
+
job_class: job_class.source,
|
|
86
|
+
runner: runner_node.method_name,
|
|
87
|
+
to: runner_node.method_name,
|
|
88
|
+
matcher: matcher_node.method_name)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def find_runner_node(node)
|
|
92
|
+
node.each_ancestor(:send).find { |ancestor| runner?(ancestor) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def runner_matcher?(matcher_node, runner_node)
|
|
96
|
+
node = runner_node.first_argument
|
|
97
|
+
node = node.receiver while node&.send_type? && node != matcher_node
|
|
98
|
+
node == matcher_node
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def runner?(node)
|
|
102
|
+
RUNNERS.include?(node.method_name)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -3,15 +3,26 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module RSpecRails
|
|
6
|
-
#
|
|
6
|
+
# Checks for time travel helpers in `around` hooks.
|
|
7
|
+
#
|
|
8
|
+
# In Rails example groups, `freeze_time` in a `before` hook does not need
|
|
9
|
+
# a block: Rails' `after_teardown` calls `travel_back` after each example.
|
|
10
|
+
# This also allows an example to use block-form time travel, which Rails
|
|
11
|
+
# does not allow inside another time travel block.
|
|
12
|
+
# See https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html
|
|
7
13
|
#
|
|
8
14
|
# @safety
|
|
9
|
-
#
|
|
10
|
-
#
|
|
15
|
+
# The automatic `travel_back` requires both
|
|
16
|
+
# `ActiveSupport::Testing::TimeHelpers` and RSpec Rails' lifecycle
|
|
17
|
+
# hooks. RSpec Rails applies those hooks to example groups with Rails
|
|
18
|
+
# `type:` metadata. File paths set that metadata only when
|
|
19
|
+
# `infer_spec_type_from_file_location!` is enabled. In a plain RSpec
|
|
20
|
+
# group that only includes TimeHelpers, use a block or call
|
|
21
|
+
# `travel_back` explicitly.
|
|
22
|
+
# See https://rspec.info/features/7-1/rspec-rails/directory-structure/
|
|
11
23
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# `around`.
|
|
24
|
+
# Autocorrection can change execution order: `around` starts before
|
|
25
|
+
# all `before` hooks, while `before` runs in normal hook order.
|
|
15
26
|
#
|
|
16
27
|
# @example
|
|
17
28
|
# # bad
|
|
@@ -29,7 +40,7 @@ module RuboCop
|
|
|
29
40
|
# # good
|
|
30
41
|
# before { freeze_time }
|
|
31
42
|
#
|
|
32
|
-
class TravelAround <
|
|
43
|
+
class TravelAround < RuboCop::Cop::Base
|
|
33
44
|
extend AutoCorrector
|
|
34
45
|
|
|
35
46
|
MSG = 'Prefer to travel in `before` rather than `around`.'
|
|
@@ -45,7 +56,7 @@ module RuboCop
|
|
|
45
56
|
(block
|
|
46
57
|
$(send nil? TRAVEL_METHOD_NAMES ...)
|
|
47
58
|
(args ...)
|
|
48
|
-
(send _ :run)
|
|
59
|
+
(send (lvar _) :run)
|
|
49
60
|
)
|
|
50
61
|
PATTERN
|
|
51
62
|
|
|
@@ -64,7 +75,7 @@ module RuboCop
|
|
|
64
75
|
)
|
|
65
76
|
PATTERN
|
|
66
77
|
|
|
67
|
-
def on_block(node)
|
|
78
|
+
def on_block(node) # rubocop:disable InternalAffairs/ItblockHandler
|
|
68
79
|
extract_run_in_travel(node) do |run_node|
|
|
69
80
|
run_in_travel(node, run_node)
|
|
70
81
|
end
|
|
@@ -76,26 +87,30 @@ module RuboCop
|
|
|
76
87
|
|
|
77
88
|
private
|
|
78
89
|
|
|
79
|
-
def run_in_travel(node,
|
|
80
|
-
around_node = extract_surrounding_around_block(
|
|
90
|
+
def run_in_travel(node, travel_node)
|
|
91
|
+
around_node = extract_surrounding_around_block(node)
|
|
81
92
|
return unless around_node
|
|
93
|
+
return unless matching_run_receiver?(node.body, around_node)
|
|
82
94
|
|
|
83
95
|
add_offense(node) do |corrector|
|
|
84
96
|
corrector.replace(node, node.body.source)
|
|
85
|
-
corrector.insert_before(
|
|
86
|
-
|
|
97
|
+
corrector.insert_before(
|
|
98
|
+
around_node,
|
|
99
|
+
"before { #{travel_node.source} }\n\n#{indent(around_node)}"
|
|
100
|
+
)
|
|
87
101
|
end
|
|
88
102
|
end
|
|
89
103
|
|
|
90
104
|
def travel_with_block_pass(node, lvar)
|
|
91
105
|
around_node = extract_surrounding_around_block(node)
|
|
92
106
|
return unless around_node
|
|
107
|
+
return unless lvar.name == around_argument_name(around_node)
|
|
93
108
|
|
|
94
109
|
add_offense(node) do |corrector|
|
|
95
110
|
corrector.replace(node, "#{lvar.name}.run")
|
|
96
111
|
corrector.insert_before(
|
|
97
112
|
around_node,
|
|
98
|
-
"before { #{node.method_name} }\n\n"
|
|
113
|
+
"before { #{node.method_name} }\n\n#{indent(around_node)}"
|
|
99
114
|
)
|
|
100
115
|
end
|
|
101
116
|
end
|
|
@@ -104,9 +119,23 @@ module RuboCop
|
|
|
104
119
|
# @return [RuboCop::AST::BlockNode, nil]
|
|
105
120
|
def extract_surrounding_around_block(node)
|
|
106
121
|
node.each_ancestor(:block).find do |ancestor|
|
|
107
|
-
match_around_each?(ancestor)
|
|
122
|
+
match_around_each?(ancestor) &&
|
|
123
|
+
direct_child_of_around_body?(node, ancestor)
|
|
108
124
|
end
|
|
109
125
|
end
|
|
126
|
+
|
|
127
|
+
def direct_child_of_around_body?(node, around_node)
|
|
128
|
+
node.parent == around_node ||
|
|
129
|
+
(node.parent.begin_type? && node.parent.parent == around_node)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def matching_run_receiver?(run_node, around_node)
|
|
133
|
+
run_node.receiver.children.first == around_argument_name(around_node)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def around_argument_name(around_node)
|
|
137
|
+
around_node.first_argument&.name
|
|
138
|
+
end
|
|
110
139
|
end
|
|
111
140
|
end
|
|
112
141
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
# Cops for the `RSpecRails` department. The department's cops are
|
|
6
|
+
# registered for lazy loading and their files are loaded on demand.
|
|
7
|
+
module RSpecRails
|
|
8
|
+
extend LazyLoader
|
|
9
|
+
|
|
10
|
+
register_cop :AvoidSetupHook, "#{__dir__}/rspec_rails/avoid_setup_hook"
|
|
11
|
+
register_cop :FileFixture, "#{__dir__}/rspec_rails/file_fixture"
|
|
12
|
+
register_cop :HaveHttpStatus, "#{__dir__}/rspec_rails/have_http_status"
|
|
13
|
+
register_cop :HttpStatus, "#{__dir__}/rspec_rails/http_status"
|
|
14
|
+
register_cop :HttpStatusNameConsistency, "#{__dir__}/rspec_rails/http_status_name_consistency"
|
|
15
|
+
register_cop :InferredSpecType, "#{__dir__}/rspec_rails/inferred_spec_type"
|
|
16
|
+
register_cop :MinitestAssertions, "#{__dir__}/rspec_rails/minitest_assertions"
|
|
17
|
+
register_cop :NegationBeValid, "#{__dir__}/rspec_rails/negation_be_valid"
|
|
18
|
+
register_cop :ReceivePerformLater, "#{__dir__}/rspec_rails/receive_perform_later"
|
|
19
|
+
register_cop :TravelAround, "#{__dir__}/rspec_rails/travel_around"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require_relative 'rspec_rails
|
|
7
|
-
require_relative 'rspec_rails/minitest_assertions'
|
|
8
|
-
require_relative 'rspec_rails/negation_be_valid'
|
|
9
|
-
require_relative 'rspec_rails/travel_around'
|
|
3
|
+
# @deprecated This file is deprecated. Cops are registered for lazy loading in
|
|
4
|
+
# `rubocop/cop/rspec_rails`; this file is kept for compatibility with code
|
|
5
|
+
# that requires it directly.
|
|
6
|
+
require_relative 'rspec_rails'
|
data/lib/rubocop-rspec_rails.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-rspec_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.33.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Quorning
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
- Yudai Takada
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: lint_roller
|
|
@@ -32,20 +32,14 @@ dependencies:
|
|
|
32
32
|
requirements:
|
|
33
33
|
- - "~>"
|
|
34
34
|
- !ruby/object:Gem::Version
|
|
35
|
-
version: '1.
|
|
36
|
-
- - ">="
|
|
37
|
-
- !ruby/object:Gem::Version
|
|
38
|
-
version: 1.72.1
|
|
35
|
+
version: '1.89'
|
|
39
36
|
type: :runtime
|
|
40
37
|
prerelease: false
|
|
41
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
42
39
|
requirements:
|
|
43
40
|
- - "~>"
|
|
44
41
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '1.
|
|
46
|
-
- - ">="
|
|
47
|
-
- !ruby/object:Gem::Version
|
|
48
|
-
version: 1.72.1
|
|
42
|
+
version: '1.89'
|
|
49
43
|
- !ruby/object:Gem::Dependency
|
|
50
44
|
name: rubocop-rspec
|
|
51
45
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -75,12 +69,16 @@ files:
|
|
|
75
69
|
- README.md
|
|
76
70
|
- config/default.yml
|
|
77
71
|
- lib/rubocop-rspec_rails.rb
|
|
72
|
+
- lib/rubocop/cop/rspec_rails.rb
|
|
78
73
|
- lib/rubocop/cop/rspec_rails/avoid_setup_hook.rb
|
|
74
|
+
- lib/rubocop/cop/rspec_rails/file_fixture.rb
|
|
79
75
|
- lib/rubocop/cop/rspec_rails/have_http_status.rb
|
|
80
76
|
- lib/rubocop/cop/rspec_rails/http_status.rb
|
|
77
|
+
- lib/rubocop/cop/rspec_rails/http_status_name_consistency.rb
|
|
81
78
|
- lib/rubocop/cop/rspec_rails/inferred_spec_type.rb
|
|
82
79
|
- lib/rubocop/cop/rspec_rails/minitest_assertions.rb
|
|
83
80
|
- lib/rubocop/cop/rspec_rails/negation_be_valid.rb
|
|
81
|
+
- lib/rubocop/cop/rspec_rails/receive_perform_later.rb
|
|
84
82
|
- lib/rubocop/cop/rspec_rails/travel_around.rb
|
|
85
83
|
- lib/rubocop/cop/rspec_rails_cops.rb
|
|
86
84
|
- lib/rubocop/rspec_rails/config_formatter.rb
|
|
@@ -110,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
110
108
|
- !ruby/object:Gem::Version
|
|
111
109
|
version: '0'
|
|
112
110
|
requirements: []
|
|
113
|
-
rubygems_version:
|
|
111
|
+
rubygems_version: 4.0.20
|
|
114
112
|
specification_version: 4
|
|
115
113
|
summary: Code style checking for RSpec Rails files
|
|
116
114
|
test_files: []
|