rubocop-rspec 3.9.0 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/config/default.yml +14 -0
  4. data/lib/rubocop/cop/rspec/around_block.rb +22 -0
  5. data/lib/rubocop/cop/rspec/contain_exactly.rb +8 -22
  6. data/lib/rubocop/cop/rspec/context_method.rb +1 -1
  7. data/lib/rubocop/cop/rspec/context_wording.rb +1 -1
  8. data/lib/rubocop/cop/rspec/described_class.rb +1 -1
  9. data/lib/rubocop/cop/rspec/discarded_matcher.rb +113 -0
  10. data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -2
  11. data/lib/rubocop/cop/rspec/empty_hook.rb +1 -1
  12. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +1 -1
  13. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  14. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +7 -2
  15. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +1 -0
  16. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +1 -1
  17. data/lib/rubocop/cop/rspec/example_length.rb +1 -1
  18. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -1
  19. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  20. data/lib/rubocop/cop/rspec/expect_actual.rb +33 -13
  21. data/lib/rubocop/cop/rspec/expect_change.rb +1 -1
  22. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -0
  23. data/lib/rubocop/cop/rspec/expect_in_let.rb +1 -1
  24. data/lib/rubocop/cop/rspec/hook_argument.rb +1 -0
  25. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -0
  26. data/lib/rubocop/cop/rspec/indexed_let.rb +1 -1
  27. data/lib/rubocop/cop/rspec/instance_spy.rb +1 -1
  28. data/lib/rubocop/cop/rspec/iterated_expectation.rb +13 -0
  29. data/lib/rubocop/cop/rspec/leading_subject.rb +1 -1
  30. data/lib/rubocop/cop/rspec/let_before_examples.rb +1 -1
  31. data/lib/rubocop/cop/rspec/let_setup.rb +1 -1
  32. data/lib/rubocop/cop/rspec/match_with_simple_regex.rb +89 -0
  33. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +1 -1
  34. data/lib/rubocop/cop/rspec/mixin/inside_example.rb +16 -0
  35. data/lib/rubocop/cop/rspec/mixin/metadata.rb +1 -0
  36. data/lib/rubocop/cop/rspec/multiple_expectations.rb +1 -1
  37. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +1 -1
  38. data/lib/rubocop/cop/rspec/multiple_subjects.rb +1 -1
  39. data/lib/rubocop/cop/rspec/named_subject.rb +1 -1
  40. data/lib/rubocop/cop/rspec/no_expectation_example.rb +1 -0
  41. data/lib/rubocop/cop/rspec/overwriting_setup.rb +1 -1
  42. data/lib/rubocop/cop/rspec/predicate_matcher.rb +1 -1
  43. data/lib/rubocop/cop/rspec/redundant_around.rb +1 -0
  44. data/lib/rubocop/cop/rspec/repeated_description.rb +1 -1
  45. data/lib/rubocop/cop/rspec/repeated_example.rb +1 -1
  46. data/lib/rubocop/cop/rspec/return_from_stub.rb +1 -1
  47. data/lib/rubocop/cop/rspec/scattered_let.rb +11 -5
  48. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  49. data/lib/rubocop/cop/rspec/shared_context.rb +1 -1
  50. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +3 -6
  51. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +17 -10
  52. data/lib/rubocop/cop/rspec/subject_declaration.rb +17 -1
  53. data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +1 -1
  54. data/lib/rubocop/cop/rspec/void_expect.rb +3 -5
  55. data/lib/rubocop/cop/rspec/yield.rb +1 -1
  56. data/lib/rubocop/cop/rspec_cops.rb +2 -0
  57. data/lib/rubocop/rspec/description_extractor.rb +1 -1
  58. data/lib/rubocop/rspec/version.rb +1 -1
  59. data/lib/rubocop-rspec.rb +1 -0
  60. metadata +27 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9159cd716027712a72b3a3e675f83a92c55070200ae0f628a8b00c7333bc99f0
4
- data.tar.gz: 5e84a7c7108c0f745cfa3e32ab75fd9fe50a1b0f2dae4ba6e0433d6cf5cd30dc
3
+ metadata.gz: b5b6ac2158196f3722c9b53e233a5f96becfced4ae47abe76ad577f3e258ff1c
4
+ data.tar.gz: be3134943d1be32c95f193ef984e27ba7ce3091b862e0e338afb8167c3e3b106
5
5
  SHA512:
6
- metadata.gz: bc41fe5a7eccbb4452fd4bc6459d03ceb5dcd8a13316245b223db3088ea25e38bfa029e834f527dbb511b80ce5b553c83673a6e6f957c974f599d7140d4f7d40
7
- data.tar.gz: 0c6a077dd4b221d68524ed4b2734727000efb97c5fb553c31016aab51f57e0049f67c701727f6d7eeb04c42ad2866b96592793bc768bac7361ca4e7ca0d81248
6
+ metadata.gz: 51df438548b237d380ada23cfc3e38d399e86951588acda134bf23abe6ad7b36d206e4eddd52457695ac74aba24ec84535074828827b953ad46ac6932315f0c4
7
+ data.tar.gz: 93eb7b7f6c8cfad2a5e816488ef9a60df934814947086b4b702d634ac9f6643dbafdd8acec8ab1eb8449c8e76e710ff794990b22213d7335e916b65054b87981
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 3.10.0 (2026-06-05)
6
+
7
+ - Add new cop `RSpec/MatchWithSimpleRegex` to suggest `include` matcher when `match` is used with simple string literals without regex-specific features. ([@bquorning])
8
+ - Add new cop `RSpec/DiscardedMatcher` to detect matchers in void context (e.g. missing `.and` between compound matchers). ([@ydakuka])
9
+ - Add support for `itblock` nodes. ([@Darhazer])
10
+ - `RSpec/ScatteredLet` now preserves the order of `let`s during auto-correction. ([@Darhazer])
11
+ - Fix a false negative for `RSpec/EmptyLineAfterFinalLet` inside `shared_examples` / `include_examples` / `it_behaves_like` blocks. ([@Darhazer])
12
+ - Fix a false positive for `RSpec/ContainExactly` when `contain_exactly` has multiple splat arguments. ([@ydah])
13
+ - Add autocorrect support for `RSpec/SubjectDeclaration`. ([@eugeneius])
14
+ - Fix false negatives for `RSpec/SpecFilePathFormat` when the expected class path only partially matches a path segment. ([@ydah])
15
+ - 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])
16
+
5
17
  ## 3.9.0 (2026-01-07)
6
18
 
7
19
  - Fix a false positive for `RSpec/LeakyLocalVariable` when variables are used only in example metadata (e.g., skip messages). ([@ydah])
@@ -988,6 +1000,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
988
1000
  [@composerinteralia]: https://github.com/composerinteralia
989
1001
  [@corsonknowles]: https://github.com/corsonknowles
990
1002
  [@corydiamand]: https://github.com/corydiamand
1003
+ [@cvx]: https://github.com/cvx
991
1004
  [@d4rky-pl]: https://github.com/d4rky-pl
992
1005
  [@darhazer]: https://github.com/Darhazer
993
1006
  [@daveworth]: https://github.com/daveworth
@@ -1006,6 +1019,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
1006
1019
  [@elebow]: https://github.com/elebow
1007
1020
  [@elisefitz15]: https://github.com/EliseFitz15
1008
1021
  [@elliterate]: https://github.com/elliterate
1022
+ [@eugeneius]: https://github.com/eugeneius
1009
1023
  [@faucct]: https://github.com/faucct
1010
1024
  [@foton]: https://github.com/foton
1011
1025
  [@francois-ferrandis]: https://github.com/francois-ferrandis
@@ -1103,6 +1117,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
1103
1117
  [@yasu551]: https://github.com/yasu551
1104
1118
  [@ybiquitous]: https://github.com/ybiquitous
1105
1119
  [@ydah]: https://github.com/ydah
1120
+ [@ydakuka]: https://github.com/ydakuka
1106
1121
  [@yevhene]: https://github.com/yevhene
1107
1122
  [@ypresto]: https://github.com/ypresto
1108
1123
  [@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
@@ -636,6 +643,13 @@ RSpec/MatchArray:
636
643
  VersionAdded: '2.19'
637
644
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchArray
638
645
 
646
+ RSpec/MatchWithSimpleRegex:
647
+ Description: Enforces the use of `include` matcher instead of `match` when the matcher
648
+ is a simple string literal without regex-specific features.
649
+ Enabled: pending
650
+ VersionAdded: '3.10'
651
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchWithSimpleRegex
652
+
639
653
  RSpec/MessageChain:
640
654
  Description: Check that chains of messages are not being stubbed.
641
655
  Enabled: true
@@ -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(*array1, *array2) }
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 match_array(array1 + array2) }
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 if node.arguments.empty?
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
- autocorrect_for_populated_array(node, corrector)
42
- end
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 node.each_ancestor(:block).any? { |block| example?(block) }
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
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
27
- return unless example_group_with_body?(node)
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
 
@@ -68,6 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  alias on_numblock on_block
71
+ alias on_itblock on_block
71
72
 
72
73
  private
73
74
 
@@ -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
- next if SKIPPED_MATCHERS.include?(matcher)
71
-
72
- add_offense(actual) do |corrector|
73
- next unless CORRECTABLE_MATCHERS.include?(matcher)
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)
@@ -88,7 +88,7 @@ module RuboCop
88
88
  end
89
89
  end
90
90
 
91
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
91
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
92
92
  return unless style == :method_call
93
93
 
94
94
  expect_change_with_block(node) do |receiver, message|
@@ -38,6 +38,7 @@ module RuboCop
38
38
  end
39
39
 
40
40
  alias on_numblock on_block
41
+ alias on_itblock on_block
41
42
 
42
43
  private
43
44
 
@@ -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
 
@@ -89,6 +89,7 @@ module RuboCop
89
89
  end
90
90
 
91
91
  alias on_numblock on_block
92
+ alias on_itblock on_block
92
93
 
93
94
  private
94
95
 
@@ -45,6 +45,7 @@ module RuboCop
45
45
  end
46
46
 
47
47
  alias on_numblock on_block
48
+ alias on_itblock on_block
48
49
 
49
50
  private
50
51
 
@@ -59,7 +59,7 @@ module RuboCop
59
59
  }
60
60
  PATTERN
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 spec_group?(node)
64
64
 
65
65
  children = node.body&.child_nodes
@@ -42,7 +42,7 @@ module RuboCop
42
42
  ...)
43
43
  PATTERN
44
44
 
45
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
45
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
46
46
  return unless example?(node)
47
47
 
48
48
  null_double(node) do |var, receiver|
@@ -38,6 +38,13 @@ module RuboCop
38
38
  )
39
39
  PATTERN
40
40
 
41
+ # @!method each_itblock?(node)
42
+ def_node_matcher :each_itblock?, <<~PATTERN
43
+ (itblock
44
+ (send ... :each) _ (...)
45
+ )
46
+ PATTERN
47
+
41
48
  # @!method expectation?(node)
42
49
  def_node_matcher :expectation?, <<~PATTERN
43
50
  (send (send nil? :expect (lvar %)) :to ...)
@@ -55,6 +62,12 @@ module RuboCop
55
62
  end
56
63
  end
57
64
 
65
+ def on_itblock(node)
66
+ each_itblock?(node) do
67
+ check_offense(node, :it)
68
+ end
69
+ end
70
+
58
71
  private
59
72
 
60
73
  def check_offense(node, argument)
@@ -37,7 +37,7 @@ module RuboCop
37
37
 
38
38
  MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
39
39
 
40
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
40
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
41
41
  return unless subject?(node)
42
42
  return unless inside_example_group?(node)
43
43
 
@@ -55,7 +55,7 @@ module RuboCop
55
55
  [RSpec::ScatteredLet]
56
56
  end
57
57
 
58
- def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
58
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler, InternalAffairs/ItblockHandler
59
59
  return unless example_group_with_body?(node)
60
60
 
61
61
  check_let_declarations(node.body) if multiline_block?(node.body)
@@ -59,7 +59,7 @@ module RuboCop
59
59
  # @!method method_called?(node)
60
60
  def_node_search :method_called?, '(send nil? %)'
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_or_shared_group_or_including?(node)
64
64
 
65
65
  unused_let_bang(node) do |let|