rubocop-rspec 3.9.0 → 3.10.2
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 +26 -0
- data/config/default.yml +18 -1
- data/lib/rubocop/cop/rspec/around_block.rb +22 -0
- data/lib/rubocop/cop/rspec/contain_exactly.rb +8 -22
- data/lib/rubocop/cop/rspec/context_method.rb +1 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
- data/lib/rubocop/cop/rspec/described_class.rb +1 -1
- data/lib/rubocop/cop/rspec/discarded_matcher.rb +113 -0
- data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -2
- data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
- data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +7 -2
- data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -0
- data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/example_length.rb +1 -1
- data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
- data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
- data/lib/rubocop/cop/rspec/expect_actual.rb +33 -13
- data/lib/rubocop/cop/rspec/expect_change.rb +46 -16
- data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -0
- data/lib/rubocop/cop/rspec/expect_in_let.rb +1 -1
- data/lib/rubocop/cop/rspec/hook_argument.rb +1 -0
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -0
- data/lib/rubocop/cop/rspec/indexed_let.rb +1 -1
- data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
- data/lib/rubocop/cop/rspec/iterated_expectation.rb +13 -0
- data/lib/rubocop/cop/rspec/leading_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/let_before_examples.rb +1 -1
- data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/match_with_simple_regex.rb +92 -0
- data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
- data/lib/rubocop/cop/rspec/mixin/inside_example.rb +16 -0
- data/lib/rubocop/cop/rspec/mixin/metadata.rb +1 -0
- data/lib/rubocop/cop/rspec/mixin/repeated_items.rb +36 -0
- data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
- data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -1
- data/lib/rubocop/cop/rspec/multiple_subjects.rb +1 -1
- data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +1 -0
- data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +1 -1
- data/lib/rubocop/cop/rspec/redundant_around.rb +1 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
- data/lib/rubocop/cop/rspec/repeated_example.rb +7 -5
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +6 -10
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +6 -10
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +8 -11
- data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
- data/lib/rubocop/cop/rspec/scattered_let.rb +11 -5
- data/lib/rubocop/cop/rspec/scattered_setup.rb +7 -9
- data/lib/rubocop/cop/rspec/shared_context.rb +47 -7
- data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +3 -6
- data/lib/rubocop/cop/rspec/spec_file_path_format.rb +20 -12
- data/lib/rubocop/cop/rspec/subject_declaration.rb +17 -1
- data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +1 -1
- data/lib/rubocop/cop/rspec/void_expect.rb +3 -5
- data/lib/rubocop/cop/rspec/yield.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +2 -0
- data/lib/rubocop/rspec/description_extractor.rb +1 -1
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop-rspec.rb +2 -0
- metadata +28 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61b047dc2dcdafc0499a7df00f8689fa68090a92b3a9897d37fee92a88655523
|
|
4
|
+
data.tar.gz: 52f2bd8775f31951731ca043c75297722f40d6fe943f5b8d9fadc9f54b6dfc66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f68a310bb36188a5bc1d0114c8c31606d3e7f280607f26a032177dd42c7fdf88b283166b8f41d3968559527985b9c3ea10f0da036b09945c3696c8632bab3a5
|
|
7
|
+
data.tar.gz: 37d2040cb0b7d48fed88ba954a4ec0601d9ba11c6a1b3547ef553e3b35f509b9dc89b23820635ed9e7048da6a7fe4611dc5167b679a3e26336b2f940c6858e73
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## Master (Unreleased)
|
|
4
4
|
|
|
5
|
+
## 3.10.2 (2026-06-06)
|
|
6
|
+
|
|
7
|
+
- Fix false positives for `RSpec/SpecFilePathFormat` when `CustomTransform` maps a namespace to an empty string. ([@sakuro])
|
|
8
|
+
- Fix `RSpec/MatchWithSimpleRegex` to ignore regular expressions with options. ([@bquorning])
|
|
9
|
+
|
|
10
|
+
## 3.10.1 (2026-06-05)
|
|
11
|
+
|
|
12
|
+
- Add `Strict` option to `RSpec/SharedContext` to flag `shared_context` whenever it contains examples, even alongside setup code. ([@Darhazer])
|
|
13
|
+
- Add `NegatedMatcher` configuration option `RSpec/ExpectChange`. ([@Darhazer])
|
|
14
|
+
- Fix `RSpec/MatchWithSimpleRegex` to ignore regular expressions with interpolations. ([@bquorning])
|
|
15
|
+
|
|
16
|
+
## 3.10.0 (2026-06-05)
|
|
17
|
+
|
|
18
|
+
- Add new cop `RSpec/MatchWithSimpleRegex` to suggest `include` matcher when `match` is used with simple string literals without regex-specific features. ([@bquorning])
|
|
19
|
+
- Add new cop `RSpec/DiscardedMatcher` to detect matchers in void context (e.g. missing `.and` between compound matchers). ([@ydakuka])
|
|
20
|
+
- Add support for `itblock` nodes. ([@Darhazer])
|
|
21
|
+
- `RSpec/ScatteredLet` now preserves the order of `let`s during auto-correction. ([@Darhazer])
|
|
22
|
+
- Fix a false negative for `RSpec/EmptyLineAfterFinalLet` inside `shared_examples` / `include_examples` / `it_behaves_like` blocks. ([@Darhazer])
|
|
23
|
+
- Fix a false positive for `RSpec/ContainExactly` when `contain_exactly` has multiple splat arguments. ([@ydah])
|
|
24
|
+
- Add autocorrect support for `RSpec/SubjectDeclaration`. ([@eugeneius])
|
|
25
|
+
- Fix false negatives for `RSpec/SpecFilePathFormat` when the expected class path only partially matches a path segment. ([@ydah])
|
|
26
|
+
- Fix a false negative for `RSpec/ExpectActual` when the matcher takes no arguments (e.g. `expect("foo").to be_present`, `expect(1).to be`). ([@cvx])
|
|
27
|
+
|
|
5
28
|
## 3.9.0 (2026-01-07)
|
|
6
29
|
|
|
7
30
|
- Fix a false positive for `RSpec/LeakyLocalVariable` when variables are used only in example metadata (e.g., skip messages). ([@ydah])
|
|
@@ -988,6 +1011,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
|
988
1011
|
[@composerinteralia]: https://github.com/composerinteralia
|
|
989
1012
|
[@corsonknowles]: https://github.com/corsonknowles
|
|
990
1013
|
[@corydiamand]: https://github.com/corydiamand
|
|
1014
|
+
[@cvx]: https://github.com/cvx
|
|
991
1015
|
[@d4rky-pl]: https://github.com/d4rky-pl
|
|
992
1016
|
[@darhazer]: https://github.com/Darhazer
|
|
993
1017
|
[@daveworth]: https://github.com/daveworth
|
|
@@ -1006,6 +1030,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
|
1006
1030
|
[@elebow]: https://github.com/elebow
|
|
1007
1031
|
[@elisefitz15]: https://github.com/EliseFitz15
|
|
1008
1032
|
[@elliterate]: https://github.com/elliterate
|
|
1033
|
+
[@eugeneius]: https://github.com/eugeneius
|
|
1009
1034
|
[@faucct]: https://github.com/faucct
|
|
1010
1035
|
[@foton]: https://github.com/foton
|
|
1011
1036
|
[@francois-ferrandis]: https://github.com/francois-ferrandis
|
|
@@ -1103,6 +1128,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
|
|
|
1103
1128
|
[@yasu551]: https://github.com/yasu551
|
|
1104
1129
|
[@ybiquitous]: https://github.com/ybiquitous
|
|
1105
1130
|
[@ydah]: https://github.com/ydah
|
|
1131
|
+
[@ydakuka]: https://github.com/ydakuka
|
|
1106
1132
|
[@yevhene]: https://github.com/yevhene
|
|
1107
1133
|
[@ypresto]: https://github.com/ypresto
|
|
1108
1134
|
[@yujideveloper]: https://github.com/yujideveloper
|
data/config/default.yml
CHANGED
|
@@ -311,6 +311,13 @@ RSpec/Dialect:
|
|
|
311
311
|
VersionAdded: '1.33'
|
|
312
312
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Dialect
|
|
313
313
|
|
|
314
|
+
RSpec/DiscardedMatcher:
|
|
315
|
+
Description: Checks for matchers that are used in void context.
|
|
316
|
+
Enabled: pending
|
|
317
|
+
VersionAdded: '3.10'
|
|
318
|
+
CustomMatcherMethods: []
|
|
319
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/DiscardedMatcher
|
|
320
|
+
|
|
314
321
|
RSpec/DuplicatedMetadata:
|
|
315
322
|
Description: Avoid duplicated metadata.
|
|
316
323
|
Enabled: true
|
|
@@ -453,7 +460,8 @@ RSpec/ExpectChange:
|
|
|
453
460
|
- block
|
|
454
461
|
SafeAutoCorrect: false
|
|
455
462
|
VersionAdded: '1.22'
|
|
456
|
-
VersionChanged: '
|
|
463
|
+
VersionChanged: '3.10'
|
|
464
|
+
NegatedMatcher: ~
|
|
457
465
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectChange
|
|
458
466
|
|
|
459
467
|
RSpec/ExpectInHook:
|
|
@@ -636,6 +644,13 @@ RSpec/MatchArray:
|
|
|
636
644
|
VersionAdded: '2.19'
|
|
637
645
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchArray
|
|
638
646
|
|
|
647
|
+
RSpec/MatchWithSimpleRegex:
|
|
648
|
+
Description: Enforces the use of `include` matcher instead of `match` when the matcher
|
|
649
|
+
is a simple string literal without regex-specific features.
|
|
650
|
+
Enabled: pending
|
|
651
|
+
VersionAdded: '3.10'
|
|
652
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchWithSimpleRegex
|
|
653
|
+
|
|
639
654
|
RSpec/MessageChain:
|
|
640
655
|
Description: Check that chains of messages are not being stubbed.
|
|
641
656
|
Enabled: true
|
|
@@ -901,7 +916,9 @@ RSpec/ScatteredSetup:
|
|
|
901
916
|
RSpec/SharedContext:
|
|
902
917
|
Description: Checks for proper shared_context and shared_examples usage.
|
|
903
918
|
Enabled: true
|
|
919
|
+
Strict: false
|
|
904
920
|
VersionAdded: '1.13'
|
|
921
|
+
VersionChanged: '3.10'
|
|
905
922
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedContext
|
|
906
923
|
|
|
907
924
|
RSpec/SharedExamples:
|
|
@@ -41,6 +41,11 @@ module RuboCop
|
|
|
41
41
|
(numblock (send nil? :around sym ?) ...)
|
|
42
42
|
PATTERN
|
|
43
43
|
|
|
44
|
+
# @!method hook_itblock(node)
|
|
45
|
+
def_node_matcher :hook_itblock, <<~PATTERN
|
|
46
|
+
(itblock (send nil? :around sym ?) ...)
|
|
47
|
+
PATTERN
|
|
48
|
+
|
|
44
49
|
# @!method find_arg_usage(node)
|
|
45
50
|
def_node_search :find_arg_usage, <<~PATTERN
|
|
46
51
|
{(send $... {:call :run}) (send _ _ $...) (yield $...) (block-pass $...)}
|
|
@@ -62,6 +67,12 @@ module RuboCop
|
|
|
62
67
|
end
|
|
63
68
|
end
|
|
64
69
|
|
|
70
|
+
def on_itblock(node)
|
|
71
|
+
hook_itblock(node) do
|
|
72
|
+
check_for_itblock(node)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
65
76
|
private
|
|
66
77
|
|
|
67
78
|
def add_no_arg_offense(node)
|
|
@@ -89,6 +100,17 @@ module RuboCop
|
|
|
89
100
|
message: format(MSG_UNUSED_ARG, arg: :_1)
|
|
90
101
|
)
|
|
91
102
|
end
|
|
103
|
+
|
|
104
|
+
def check_for_itblock(block)
|
|
105
|
+
find_arg_usage(block) do |usage|
|
|
106
|
+
return if usage.include?(s(:lvar, :it))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
add_offense(
|
|
110
|
+
block.children.last,
|
|
111
|
+
message: format(MSG_UNUSED_ARG, arg: :it)
|
|
112
|
+
)
|
|
113
|
+
end
|
|
92
114
|
end
|
|
93
115
|
end
|
|
94
116
|
end
|
|
@@ -12,10 +12,13 @@ module RuboCop
|
|
|
12
12
|
#
|
|
13
13
|
# @example
|
|
14
14
|
# # bad
|
|
15
|
-
# it { is_expected.to contain_exactly(*
|
|
15
|
+
# it { is_expected.to contain_exactly(*array) }
|
|
16
|
+
#
|
|
17
|
+
# # good
|
|
18
|
+
# it { is_expected.to match_array(array) }
|
|
16
19
|
#
|
|
17
20
|
# # good
|
|
18
|
-
# it { is_expected.to
|
|
21
|
+
# it { is_expected.to contain_exactly(*array1, *array2) }
|
|
19
22
|
#
|
|
20
23
|
# # good
|
|
21
24
|
# it { is_expected.to contain_exactly(content, *array) }
|
|
@@ -27,29 +30,12 @@ module RuboCop
|
|
|
27
30
|
RESTRICT_ON_SEND = %i[contain_exactly].freeze
|
|
28
31
|
|
|
29
32
|
def on_send(node)
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
check_populated_collection(node)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def check_populated_collection(node)
|
|
38
|
-
return unless node.each_child_node.all?(&:splat_type?)
|
|
33
|
+
return unless node.arguments.one? && node.first_argument.splat_type?
|
|
39
34
|
|
|
40
35
|
add_offense(node) do |corrector|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def autocorrect_for_populated_array(node, corrector)
|
|
46
|
-
arrays = node.arguments.map do |splat_node|
|
|
47
|
-
splat_node.children.first
|
|
36
|
+
array = node.first_argument.children.first
|
|
37
|
+
corrector.replace(node, "match_array(#{array.source})")
|
|
48
38
|
end
|
|
49
|
-
corrector.replace(
|
|
50
|
-
node,
|
|
51
|
-
"match_array(#{arrays.map(&:source).join(' + ')})"
|
|
52
|
-
)
|
|
53
39
|
end
|
|
54
40
|
end
|
|
55
41
|
end
|
|
@@ -38,7 +38,7 @@ module RuboCop
|
|
|
38
38
|
...)
|
|
39
39
|
PATTERN
|
|
40
40
|
|
|
41
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
41
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
42
42
|
context_method(node) do |context|
|
|
43
43
|
add_offense(context) do |corrector|
|
|
44
44
|
corrector.replace(node.send_node.loc.selector, 'describe')
|
|
@@ -70,7 +70,7 @@ module RuboCop
|
|
|
70
70
|
(block (send #rspec? { :context :shared_context } $(any_str ...) ...) ...)
|
|
71
71
|
PATTERN
|
|
72
72
|
|
|
73
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
73
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
74
74
|
context_wording(node) do |context|
|
|
75
75
|
unless matches_allowed_pattern?(description(context))
|
|
76
76
|
add_offense(context, message: message)
|
|
@@ -110,7 +110,7 @@ module RuboCop
|
|
|
110
110
|
def_node_search :contains_described_class?,
|
|
111
111
|
'(send nil? :described_class)'
|
|
112
112
|
|
|
113
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
113
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
114
114
|
# In case the explicit style is used, we need to remember what's
|
|
115
115
|
# being described.
|
|
116
116
|
@described_class, body = described_constant(node)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Checks for matchers that are used in void context.
|
|
7
|
+
#
|
|
8
|
+
# Matcher calls like `change`, `receive`, etc. that appear as
|
|
9
|
+
# standalone expressions have their result silently discarded.
|
|
10
|
+
# This usually means a missing `.and` to chain compound matchers.
|
|
11
|
+
#
|
|
12
|
+
# The list of matcher methods can be configured
|
|
13
|
+
# with `CustomMatcherMethods`.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# # bad
|
|
17
|
+
# specify do
|
|
18
|
+
# expect { result }
|
|
19
|
+
# .to change { obj.foo }.from(1).to(2)
|
|
20
|
+
# change { obj.bar }.from(3).to(4)
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# # good
|
|
24
|
+
# specify do
|
|
25
|
+
# expect { result }
|
|
26
|
+
# .to change { obj.foo }.from(1).to(2)
|
|
27
|
+
# .and change { obj.bar }.from(3).to(4)
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # good
|
|
31
|
+
# specify do
|
|
32
|
+
# expect { result }.to change { obj.foo }.from(1).to(2)
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
class DiscardedMatcher < Base
|
|
36
|
+
include InsideExample
|
|
37
|
+
|
|
38
|
+
MSG = 'The result of `%<method>s` is not used. ' \
|
|
39
|
+
'Did you mean to chain it with `.and`?'
|
|
40
|
+
|
|
41
|
+
MATCHER_METHODS = %i[
|
|
42
|
+
change have_received output
|
|
43
|
+
receive receive_messages receive_message_chain
|
|
44
|
+
].to_set.freeze
|
|
45
|
+
|
|
46
|
+
def on_send(node)
|
|
47
|
+
check_discarded_matcher(node, node)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
51
|
+
check_discarded_matcher(node.send_node, node)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def check_discarded_matcher(send_node, node)
|
|
57
|
+
return unless matcher_call?(send_node)
|
|
58
|
+
return unless inside_example?(node)
|
|
59
|
+
return unless example_with_matcher_expectation?(node)
|
|
60
|
+
|
|
61
|
+
target = find_outermost_chain(node)
|
|
62
|
+
return unless void_value?(target)
|
|
63
|
+
|
|
64
|
+
add_offense(target, message: format(MSG, method: node.method_name))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def example_with_matcher_expectation?(node)
|
|
68
|
+
example_node =
|
|
69
|
+
node.each_ancestor(:block).find { |ancestor| example?(ancestor) }
|
|
70
|
+
|
|
71
|
+
example_node.each_descendant(:send).any? do |send_node|
|
|
72
|
+
expectation_with_matcher?(send_node)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def expectation_with_matcher?(node)
|
|
77
|
+
%i[to to_not not_to].include?(node.method_name) &&
|
|
78
|
+
node.arguments.any? do |arg|
|
|
79
|
+
arg.each_node(:send).any? { |s| matcher_call?(s) }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def void_value?(node)
|
|
84
|
+
case node.parent.type
|
|
85
|
+
when :block
|
|
86
|
+
example?(node.parent)
|
|
87
|
+
when :begin, :case, :when
|
|
88
|
+
void_value?(node.parent)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def matcher_call?(node)
|
|
93
|
+
node.receiver.nil? && all_matcher_methods.include?(node.method_name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def all_matcher_methods
|
|
97
|
+
@all_matcher_methods ||=
|
|
98
|
+
(MATCHER_METHODS + custom_matcher_methods).freeze
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def custom_matcher_methods
|
|
102
|
+
cop_config.fetch('CustomMatcherMethods', []).map(&:to_sym)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def find_outermost_chain(node)
|
|
106
|
+
current = node
|
|
107
|
+
current = current.parent while current.parent.receiver == current
|
|
108
|
+
current
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -38,6 +38,7 @@ module RuboCop
|
|
|
38
38
|
class EmptyExampleGroup < Base
|
|
39
39
|
extend AutoCorrector
|
|
40
40
|
|
|
41
|
+
include InsideExample
|
|
41
42
|
include RangeHelp
|
|
42
43
|
|
|
43
44
|
MSG = 'Empty example group detected.'
|
|
@@ -136,9 +137,9 @@ module RuboCop
|
|
|
136
137
|
}
|
|
137
138
|
PATTERN
|
|
138
139
|
|
|
139
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
140
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
140
141
|
return if node.each_ancestor(:any_def).any?
|
|
141
|
-
return if
|
|
142
|
+
return if inside_example?(node)
|
|
142
143
|
|
|
143
144
|
example_group_body(node) do |body|
|
|
144
145
|
next unless offensive?(body)
|
|
@@ -34,7 +34,7 @@ module RuboCop
|
|
|
34
34
|
(block $(send nil? #Hooks.all ...) _ nil?)
|
|
35
35
|
PATTERN
|
|
36
36
|
|
|
37
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
37
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
38
38
|
empty_hook?(node) do |hook|
|
|
39
39
|
add_offense(hook) do |corrector|
|
|
40
40
|
corrector.remove(
|
|
@@ -46,7 +46,7 @@ module RuboCop
|
|
|
46
46
|
|
|
47
47
|
MSG = 'Add an empty line after `%<example>s`.'
|
|
48
48
|
|
|
49
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
49
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
50
50
|
return unless example?(node)
|
|
51
51
|
return if allowed_one_liner?(node)
|
|
52
52
|
|
|
@@ -29,7 +29,7 @@ module RuboCop
|
|
|
29
29
|
|
|
30
30
|
MSG = 'Add an empty line after `%<example_group>s`.'
|
|
31
31
|
|
|
32
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
32
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
33
33
|
return unless spec_group?(node)
|
|
34
34
|
|
|
35
35
|
missing_separating_line_offense(node) do |method|
|
|
@@ -23,8 +23,13 @@ module RuboCop
|
|
|
23
23
|
|
|
24
24
|
MSG = 'Add an empty line after the last `%<let>s`.'
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# @!method example_group_or_include?(node)
|
|
27
|
+
def_node_matcher :example_group_or_include?, <<~PATTERN
|
|
28
|
+
(block (send #rspec? {#SharedGroups.all #ExampleGroups.all #Includes.all} ...) args $_)
|
|
29
|
+
PATTERN
|
|
30
|
+
|
|
31
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
32
|
+
return unless example_group_or_include?(node)
|
|
28
33
|
|
|
29
34
|
final_let = node.body.child_nodes.reverse.find { |child| let?(child) }
|
|
30
35
|
|
|
@@ -22,7 +22,7 @@ module RuboCop
|
|
|
22
22
|
|
|
23
23
|
MSG = 'Add an empty line after `%<subject>s`.'
|
|
24
24
|
|
|
25
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
25
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
26
26
|
return unless subject?(node)
|
|
27
27
|
return unless inside_example_group?(node)
|
|
28
28
|
|
|
@@ -59,7 +59,7 @@ module RuboCop
|
|
|
59
59
|
|
|
60
60
|
LABEL = 'Example'
|
|
61
61
|
|
|
62
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
62
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
63
63
|
return unless example?(node)
|
|
64
64
|
|
|
65
65
|
check_code_length(node)
|
|
@@ -66,7 +66,7 @@ module RuboCop
|
|
|
66
66
|
# @!method example_description(node)
|
|
67
67
|
def_node_matcher :example_description, '(send nil? _ $(str $_))'
|
|
68
68
|
|
|
69
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
69
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
70
70
|
return unless example?(node)
|
|
71
71
|
|
|
72
72
|
check_example_without_description(node.send_node)
|
|
@@ -78,7 +78,7 @@ module RuboCop
|
|
|
78
78
|
} ...) ...)
|
|
79
79
|
PATTERN
|
|
80
80
|
|
|
81
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
81
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
82
82
|
it_description(node) do |description_node, message|
|
|
83
83
|
if message.match?(SHOULD_PREFIX)
|
|
84
84
|
add_wording_offense(description_node, MSG_SHOULD)
|
|
@@ -19,12 +19,15 @@ module RuboCop
|
|
|
19
19
|
# expect(name).to eq("John")
|
|
20
20
|
#
|
|
21
21
|
# # bad (not supported autocorrection)
|
|
22
|
+
# expect(42).to be_even
|
|
22
23
|
# expect(false).to eq(true)
|
|
24
|
+
# expect("user").to be_present
|
|
23
25
|
#
|
|
24
26
|
class ExpectActual < Base
|
|
25
27
|
extend AutoCorrector
|
|
26
28
|
|
|
27
29
|
MSG = 'Provide the actual value you are testing to `expect(...)`.'
|
|
30
|
+
MSG_NO_ARG = 'Test a non-literal value with `expect(...)`.'
|
|
28
31
|
|
|
29
32
|
RESTRICT_ON_SEND = Runners.all
|
|
30
33
|
|
|
@@ -65,26 +68,43 @@ module RuboCop
|
|
|
65
68
|
)
|
|
66
69
|
PATTERN
|
|
67
70
|
|
|
71
|
+
# @!method expect_literal_no_arg(node)
|
|
72
|
+
def_node_matcher :expect_literal_no_arg, <<~PATTERN
|
|
73
|
+
(send
|
|
74
|
+
(send nil? :expect $#literal?)
|
|
75
|
+
#Runners.all
|
|
76
|
+
$(send nil? $_)
|
|
77
|
+
)
|
|
78
|
+
PATTERN
|
|
79
|
+
|
|
68
80
|
def on_send(node)
|
|
69
81
|
expect_literal(node) do |actual, send_node, matcher, expected|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
next if literal?(expected)
|
|
75
|
-
|
|
76
|
-
corrector.replace(actual, expected.source)
|
|
77
|
-
if matcher == :be
|
|
78
|
-
corrector.replace(expected, actual.source)
|
|
79
|
-
else
|
|
80
|
-
corrector.replace(send_node, "#{matcher}(#{actual.source})")
|
|
81
|
-
end
|
|
82
|
-
end
|
|
82
|
+
register_offense(actual, send_node, matcher, expected)
|
|
83
|
+
end
|
|
84
|
+
expect_literal_no_arg(node) do |actual, send_node, matcher|
|
|
85
|
+
register_offense(actual, send_node, matcher, nil)
|
|
83
86
|
end
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
private
|
|
87
90
|
|
|
91
|
+
def register_offense(actual, send_node, matcher, expected)
|
|
92
|
+
return if SKIPPED_MATCHERS.include?(matcher)
|
|
93
|
+
|
|
94
|
+
message = expected.nil? ? MSG_NO_ARG : MSG
|
|
95
|
+
add_offense(actual, message: message) do |corrector|
|
|
96
|
+
next unless CORRECTABLE_MATCHERS.include?(matcher)
|
|
97
|
+
next if expected.nil? || literal?(expected)
|
|
98
|
+
|
|
99
|
+
corrector.replace(actual, expected.source)
|
|
100
|
+
if matcher == :be
|
|
101
|
+
corrector.replace(expected, actual.source)
|
|
102
|
+
else
|
|
103
|
+
corrector.replace(send_node, "#{matcher}(#{actual.source})")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
88
108
|
# This is not implemented using a NodePattern because it seems
|
|
89
109
|
# to not be able to match against an explicit (nil) sexp
|
|
90
110
|
def literal?(node)
|
|
@@ -10,6 +10,10 @@ module RuboCop
|
|
|
10
10
|
#
|
|
11
11
|
# This cop can be configured using the `EnforcedStyle` option.
|
|
12
12
|
#
|
|
13
|
+
# When using compound expectations with `change` and a negated matcher
|
|
14
|
+
# (e.g., `not_change`), you can configure the `NegatedMatcher` option
|
|
15
|
+
# to ensure consistent style enforcement across both matchers.
|
|
16
|
+
#
|
|
13
17
|
# @safety
|
|
14
18
|
# Autocorrection is unsafe because `method_call` style calls the
|
|
15
19
|
# receiver *once* and sends the message to it before and after
|
|
@@ -48,23 +52,29 @@ module RuboCop
|
|
|
48
52
|
# # good
|
|
49
53
|
# expect { run }.to change { Foo.bar }
|
|
50
54
|
#
|
|
55
|
+
# @example `NegatedMatcher: not_change` (with compound expectations)
|
|
56
|
+
# # bad
|
|
57
|
+
# expect { run }.to change(Foo, :bar).and not_change { Foo.baz }
|
|
58
|
+
#
|
|
59
|
+
# # good
|
|
60
|
+
# expect { run }.to change(Foo, :bar).and not_change(Foo, :baz)
|
|
61
|
+
#
|
|
51
62
|
class ExpectChange < Base
|
|
52
63
|
extend AutoCorrector
|
|
53
64
|
include ConfigurableEnforcedStyle
|
|
54
65
|
|
|
55
|
-
MSG_BLOCK = 'Prefer
|
|
56
|
-
MSG_CALL = 'Prefer
|
|
57
|
-
RESTRICT_ON_SEND = %i[change].freeze
|
|
66
|
+
MSG_BLOCK = 'Prefer `%<matcher>s(%<obj>s, :%<attr>s)`.'
|
|
67
|
+
MSG_CALL = 'Prefer `%<matcher>s { %<obj>s.%<attr>s }`.'
|
|
58
68
|
|
|
59
|
-
# @!method
|
|
60
|
-
def_node_matcher :
|
|
61
|
-
(send nil?
|
|
69
|
+
# @!method expect_matcher_with_arguments(node)
|
|
70
|
+
def_node_matcher :expect_matcher_with_arguments, <<~PATTERN
|
|
71
|
+
(send nil? _ $_ ({sym str} $_))
|
|
62
72
|
PATTERN
|
|
63
73
|
|
|
64
|
-
# @!method
|
|
65
|
-
def_node_matcher :
|
|
74
|
+
# @!method expect_matcher_with_block(node)
|
|
75
|
+
def_node_matcher :expect_matcher_with_block, <<~PATTERN
|
|
66
76
|
(block
|
|
67
|
-
(send nil?
|
|
77
|
+
(send nil? _)
|
|
68
78
|
(args)
|
|
69
79
|
(send
|
|
70
80
|
${
|
|
@@ -78,27 +88,47 @@ module RuboCop
|
|
|
78
88
|
|
|
79
89
|
def on_send(node)
|
|
80
90
|
return unless style == :block
|
|
91
|
+
return unless matcher_method?(node.method_name)
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
expect_matcher_with_arguments(node) do |receiver, message|
|
|
94
|
+
matcher_name = node.method_name.to_s
|
|
95
|
+
msg = format(MSG_CALL, matcher: matcher_name,
|
|
96
|
+
obj: receiver.source, attr: message)
|
|
84
97
|
add_offense(node, message: msg) do |corrector|
|
|
85
|
-
replacement = "
|
|
98
|
+
replacement = "#{matcher_name} { #{receiver.source}.#{message} }"
|
|
86
99
|
corrector.replace(node, replacement)
|
|
87
100
|
end
|
|
88
101
|
end
|
|
89
102
|
end
|
|
90
103
|
|
|
91
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
104
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
92
105
|
return unless style == :method_call
|
|
106
|
+
return unless matcher_method?(node.method_name)
|
|
93
107
|
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
expect_matcher_with_block(node) do |receiver, message|
|
|
109
|
+
matcher_name = node.method_name.to_s
|
|
110
|
+
msg = format(MSG_BLOCK, matcher: matcher_name,
|
|
111
|
+
obj: receiver.source, attr: message)
|
|
96
112
|
add_offense(node, message: msg) do |corrector|
|
|
97
|
-
replacement = "
|
|
113
|
+
replacement = "#{matcher_name}(#{receiver.source}, :#{message})"
|
|
98
114
|
corrector.replace(node, replacement)
|
|
99
115
|
end
|
|
100
116
|
end
|
|
101
117
|
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def matcher_method_names
|
|
122
|
+
[:change, negated_matcher&.to_sym].compact
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def matcher_method?(method_name)
|
|
126
|
+
matcher_method_names.include?(method_name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def negated_matcher
|
|
130
|
+
cop_config['NegatedMatcher']
|
|
131
|
+
end
|
|
102
132
|
end
|
|
103
133
|
end
|
|
104
134
|
end
|
|
@@ -22,7 +22,7 @@ module RuboCop
|
|
|
22
22
|
# @!method expectation(node)
|
|
23
23
|
def_node_search :expectation, '(send nil? #Expectations.all ...)'
|
|
24
24
|
|
|
25
|
-
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
|
|
25
|
+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
|
|
26
26
|
return unless let?(node)
|
|
27
27
|
return if node.body.nil?
|
|
28
28
|
|