rubocop-rspec 3.5.0 → 3.7.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/config/default.yml +16 -1
  4. data/lib/rubocop/cop/rspec/change_by_zero.rb +3 -2
  5. data/lib/rubocop/cop/rspec/context_wording.rb +6 -1
  6. data/lib/rubocop/cop/rspec/described_class.rb +25 -12
  7. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +1 -1
  8. data/lib/rubocop/cop/rspec/dialect.rb +1 -0
  9. data/lib/rubocop/cop/rspec/empty_example_group.rb +4 -4
  10. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +1 -1
  11. data/lib/rubocop/cop/rspec/expect_change.rb +21 -2
  12. data/lib/rubocop/cop/rspec/focus.rb +1 -1
  13. data/lib/rubocop/cop/rspec/hook_argument.rb +2 -2
  14. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -1
  15. data/lib/rubocop/cop/rspec/include_examples.rb +90 -0
  16. data/lib/rubocop/cop/rspec/iterated_expectation.rb +32 -10
  17. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +7 -0
  18. data/lib/rubocop/cop/rspec/nested_groups.rb +2 -2
  19. data/lib/rubocop/cop/rspec/no_expectation_example.rb +1 -1
  20. data/lib/rubocop/cop/rspec/pending.rb +1 -1
  21. data/lib/rubocop/cop/rspec/pending_without_reason.rb +2 -3
  22. data/lib/rubocop/cop/rspec/predicate_matcher.rb +1 -1
  23. data/lib/rubocop/cop/rspec/receive_messages.rb +2 -3
  24. data/lib/rubocop/cop/rspec/redundant_around.rb +1 -1
  25. data/lib/rubocop/cop/rspec/scattered_setup.rb +10 -2
  26. data/lib/rubocop/cop/rspec/variable_definition.rb +1 -1
  27. data/lib/rubocop/cop/rspec/variable_name.rb +1 -1
  28. data/lib/rubocop/cop/rspec_cops.rb +1 -0
  29. data/lib/rubocop/rspec/hook.rb +3 -1
  30. data/lib/rubocop/rspec/language.rb +13 -10
  31. data/lib/rubocop/rspec/version.rb +1 -1
  32. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5852eb2103d4a2993b2174b7e4c97f5f7f09041c49407122ab6568d744d52c09
4
- data.tar.gz: 710d277b8be358c99cadf4ca500141d23fa73347ba3f29db0585a1e7026abd73
3
+ metadata.gz: 645fbfddd891af3477e31993bc8da92fc57300cbcfcd8274ee0f68cfbbe97983
4
+ data.tar.gz: 6a97e91acb933ecbe09691c090efbff715ca12adcaef7027e8d324411ec0d78d
5
5
  SHA512:
6
- metadata.gz: e27d20e6945c05f12af9fc24e17e42691186b332611d96eb9e7a5444476de44209517ccdb4829096cff659e7a7d1b98941ff632b3f7f5a547aa4dc82d3e18e38
7
- data.tar.gz: 93a9cb481828abc2e313129cec383d38de5261f382d7ae6fcabe42bd0146b5a134b90991df47cc0bade3c1e4266cbfcf733f35ea7bee8eafaa3e5e4236a0b3ca
6
+ metadata.gz: ca10e4b0b7e285136c60aa0035a018407263968fcced15155999ef5e2b6ac4edac45bcbd6c82dd355616a40ef110f9191cc986d5a8922aaab42c8118bff55c89
7
+ data.tar.gz: 7a22acf8143f309128504884a1634e23504b40821aabf14d9b20392c6c11ce7a34ad3ab48945e1b023594a794677bdf6526e7236f705fd331ab244a746375453
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 3.7.0 (2025-09-01)
6
+
7
+ - Mark `RSpec/IncludeExamples` as `SafeAutoCorrect: false`. ([@yujideveloper])
8
+ - Fix a false positive for `RSpec/LeakyConstantDeclaration` when defining constants in explicit namespaces. ([@naveg])
9
+ - Add support for error matchers (`raise_exception` and `raise_error`) to `RSpec/Dialect`. ([@lovro-bikic])
10
+ - Don't register offenses for `RSpec/DescribedClass` within `Data.define` blocks. ([@lovro-bikic])
11
+ - Add autocorrection support for `RSpec/IteratedExpectation` for single expectations. ([@lovro-bikic])
12
+ - Exclude all cops from inspecting factorybot files, except if explicitly included. ([@Mth0158])
13
+ - Fix a false positive for `RSpec/ExcessiveDocstringSpacing` when receivers are not RSpec methods. ([@ydah])
14
+
15
+ ## 3.6.0 (2025-04-18)
16
+
17
+ - Fix false positive in `RSpec/Pending`, where it would mark the default block `it` as an offense. ([@bquorning])
18
+ - Fix issue when `Style/ContextWording` is configured with a Prefix being interpreted as a boolean, like `on`. ([@sakuro])
19
+ - Add new `RSpec/IncludeExamples` cop to enforce using `it_behaves_like` over `include_examples`. ([@dvandersluis])
20
+ - Change `RSpec/ScatteredSetup` to allow `around` hooks to be scattered. ([@ydah])
21
+ - Fix an error `RSpec/ChangeByZero` cop when without expect block. ([@lee266])
22
+ - Fix a false positive for `RSpec/DescribedClass` when `SkipBlocks` is true and numblocks are used. ([@earlopain])
23
+
5
24
  ## 3.5.0 (2025-02-16)
6
25
 
7
26
  - Don't let `RSpec/PredicateMatcher` replace `respond_to?` with two arguments with the RSpec `respond_to` matcher. ([@bquorning])
@@ -998,6 +1017,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
998
1017
  [@krororo]: https://github.com/krororo
999
1018
  [@kuahyeow]: https://github.com/kuahyeow
1000
1019
  [@lazycoder9]: https://github.com/lazycoder9
1020
+ [@lee266]: https://github.com/lee266
1001
1021
  [@leoarnold]: https://github.com/leoarnold
1002
1022
  [@liberatys]: https://github.com/Liberatys
1003
1023
  [@lokhi]: https://github.com/lokhi
@@ -1011,6 +1031,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
1011
1031
  [@mlarraz]: https://github.com/mlarraz
1012
1032
  [@mockdeep]: https://github.com/mockdeep
1013
1033
  [@mothonmars]: https://github.com/MothOnMars
1034
+ [@mth0158]: https://github.com/Mth0158
1014
1035
  [@mvz]: https://github.com/mvz
1015
1036
  [@naveg]: https://github.com/naveg
1016
1037
  [@nc-holodakg]: https://github.com/nc-holodakg
@@ -1038,6 +1059,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
1038
1059
  [@rrosenblum]: https://github.com/rrosenblum
1039
1060
  [@rspeicher]: https://github.com/rspeicher
1040
1061
  [@rst-j]: https://github.com/RST-J
1062
+ [@sakuro]: https://github.com/sakuro
1041
1063
  [@samrjenkins]: https://github.com/samrjenkins
1042
1064
  [@schmijos]: https://github.com/schmijos
1043
1065
  [@seanpdoyle]: https://github.com/seanpdoyle
@@ -1062,5 +1084,6 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
1062
1084
  [@ydah]: https://github.com/ydah
1063
1085
  [@yevhene]: https://github.com/yevhene
1064
1086
  [@ypresto]: https://github.com/ypresto
1087
+ [@yujideveloper]: https://github.com/yujideveloper
1065
1088
  [@zdennis]: https://github.com/zdennis
1066
1089
  [@zverok]: https://github.com/zverok
data/config/default.yml CHANGED
@@ -6,6 +6,10 @@ RSpec:
6
6
  Include:
7
7
  - "**/*_spec.rb"
8
8
  - "**/spec/**/*"
9
+ Exclude:
10
+ - "**/spec/factories.rb"
11
+ - "**/spec/factories/**/*.rb"
12
+ - "**/features/support/factories/**/*.rb"
9
13
  Language:
10
14
  inherit_mode:
11
15
  merge:
@@ -79,6 +83,9 @@ RSpec:
79
83
  - prepend_after
80
84
  - after
81
85
  - append_after
86
+ ErrorMatchers:
87
+ - raise_error
88
+ - raise_exception
82
89
  Includes:
83
90
  inherit_mode:
84
91
  merge:
@@ -532,6 +539,14 @@ RSpec/ImplicitSubject:
532
539
  VersionChanged: '2.13'
533
540
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
534
541
 
542
+ RSpec/IncludeExamples:
543
+ Description: Checks for usage of `include_examples`.
544
+ Enabled: pending
545
+ SafeAutoCorrect: false
546
+ VersionAdded: '3.6'
547
+ VersionChanged: '3.7'
548
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IncludeExamples
549
+
535
550
  RSpec/IndexedLet:
536
551
  Description: Do not set up test data using indexes (e.g., `item_1`, `item_2`).
537
552
  Enabled: true
@@ -776,7 +791,7 @@ RSpec/ReceiveCounts:
776
791
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts
777
792
 
778
793
  RSpec/ReceiveMessages:
779
- Description: Checks for multiple messages stubbed on the same object.
794
+ Description: Prefer `receive_messages` over multiple `receive`s on the same object.
780
795
  Enabled: true
781
796
  SafeAutoCorrect: false
782
797
  VersionAdded: '2.23'
@@ -102,6 +102,8 @@ module RuboCop
102
102
  private
103
103
 
104
104
  def register_offense(node, change_node)
105
+ return unless node.parent.send_type?
106
+
105
107
  if compound_expectations?(node)
106
108
  add_offense(node,
107
109
  message: message_compound(change_node)) do |corrector|
@@ -116,8 +118,7 @@ module RuboCop
116
118
  end
117
119
 
118
120
  def compound_expectations?(node)
119
- node.parent.send_type? &&
120
- %i[and or & |].include?(node.parent.method_name)
121
+ %i[and or & |].include?(node.parent.method_name)
121
122
  end
122
123
 
123
124
  def message(change_node)
@@ -115,7 +115,12 @@ module RuboCop
115
115
  end
116
116
 
117
117
  def prefixes
118
- Array(cop_config.fetch('Prefixes', []))
118
+ Array(cop_config.fetch('Prefixes', [])).tap do |prefixes|
119
+ non_strings = prefixes.reject { |pre| pre.is_a?(String) }
120
+ unless non_strings.empty?
121
+ raise "Non-string prefixes #{non_strings.inspect} detected."
122
+ end
123
+ end
119
124
  end
120
125
  end
121
126
  end
@@ -13,6 +13,20 @@ module RuboCop
13
13
  # `OnlyStaticConstants` is only relevant when `EnforcedStyle` is
14
14
  # `described_class`.
15
15
  #
16
+ # There's a known caveat with rspec-rails's `controller` helper that
17
+ # runs its block in a different context, and `described_class` is not
18
+ # available to it. `SkipBlocks` option excludes detection in all
19
+ # non-RSpec related blocks.
20
+ #
21
+ # To narrow down this setting to only a specific directory, it is
22
+ # possible to use an overriding configuration file local to that
23
+ # directory.
24
+ #
25
+ # @safety
26
+ # Autocorrection is unsafe when `SkipBlocks: false` because
27
+ # `described_class` might not be available within the block (for
28
+ # example, in rspec-rails's `controller` helper).
29
+ #
16
30
  # @example `EnforcedStyle: described_class` (default)
17
31
  # # bad
18
32
  # describe MyClass do
@@ -47,15 +61,6 @@ module RuboCop
47
61
  # subject { MyClass.do_something }
48
62
  # end
49
63
  #
50
- # There's a known caveat with rspec-rails's `controller` helper that
51
- # runs its block in a different context, and `described_class` is not
52
- # available to it. `SkipBlocks` option excludes detection in all
53
- # non-RSpec related blocks.
54
- #
55
- # To narrow down this setting to only a specific directory, it is
56
- # possible to use an overriding configuration file local to that
57
- # directory.
58
- #
59
64
  # @example `SkipBlocks: true`
60
65
  # # spec/controllers/.rubocop.yml
61
66
  # # RSpec/DescribedClass:
@@ -78,12 +83,18 @@ module RuboCop
78
83
 
79
84
  # @!method common_instance_exec_closure?(node)
80
85
  def_node_matcher :common_instance_exec_closure?, <<~PATTERN
81
- (block (send (const nil? {:Class :Module :Struct}) :new ...) ...)
86
+ (block
87
+ {
88
+ (send (const nil? {:Class :Module :Struct}) :new ...)
89
+ (send (const nil? :Data) :define ...)
90
+ }
91
+ ...
92
+ )
82
93
  PATTERN
83
94
 
84
95
  # @!method rspec_block?(node)
85
96
  def_node_matcher :rspec_block?,
86
- '({block numblock} (send #rspec? #ALL.all ...) ...)'
97
+ '(any_block (send #rspec? #ALL.all ...) ...)'
87
98
 
88
99
  # @!method scope_changing_syntax?(node)
89
100
  def_node_matcher :scope_changing_syntax?, '{def class module}'
@@ -153,7 +164,9 @@ module RuboCop
153
164
  end
154
165
 
155
166
  def skippable_block?(node)
156
- node.block_type? && !rspec_block?(node) && cop_config['SkipBlocks']
167
+ return false unless cop_config['SkipBlocks']
168
+
169
+ node.any_block_type? && !rspec_block?(node)
157
170
  end
158
171
 
159
172
  def only_static_constants?
@@ -24,7 +24,7 @@ module RuboCop
24
24
 
25
25
  # @!method include_rspec_blocks?(node)
26
26
  def_node_search :include_rspec_blocks?, <<~PATTERN
27
- ({block numblock} (send #explicit_rspec? #ExampleGroups.all ...) ...)
27
+ (any_block (send #explicit_rspec? #ExampleGroups.all ...) ...)
28
28
  PATTERN
29
29
 
30
30
  def on_module(node)
@@ -21,6 +21,7 @@ module RuboCop
21
21
  # - let, let!
22
22
  # - subject, subject!
23
23
  # - expect, is_expected, expect_any_instance_of
24
+ # - raise_error, raise_exception
24
25
  #
25
26
  # By default all of the RSpec methods and aliases are allowed. By setting
26
27
  # a config like:
@@ -137,7 +137,7 @@ module RuboCop
137
137
  PATTERN
138
138
 
139
139
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
140
- return if node.each_ancestor(:def, :defs).any?
140
+ return if node.each_ancestor(:any_def).any?
141
141
  return if node.each_ancestor(:block).any? { |block| example?(block) }
142
142
 
143
143
  example_group_body(node) do |body|
@@ -155,7 +155,7 @@ module RuboCop
155
155
  return true unless body
156
156
  return false if conditionals_with_examples?(body)
157
157
 
158
- if body.if_type? || body.case_type?
158
+ if body.type?(:if, :case)
159
159
  !examples_in_branches?(body)
160
160
  else
161
161
  !examples?(body)
@@ -163,7 +163,7 @@ module RuboCop
163
163
  end
164
164
 
165
165
  def conditionals_with_examples?(body)
166
- return false unless body.begin_type? || body.case_type?
166
+ return false unless body.type?(:begin, :case)
167
167
 
168
168
  body.each_descendant(:if, :case).any? do |condition_node|
169
169
  examples_in_branches?(condition_node)
@@ -172,7 +172,7 @@ module RuboCop
172
172
 
173
173
  def examples_in_branches?(condition_node)
174
174
  return false unless condition_node
175
- return false if !condition_node.if_type? && !condition_node.case_type?
175
+ return false unless condition_node.type?(:if, :case)
176
176
 
177
177
  condition_node.branches.any? { |branch| examples?(branch) }
178
178
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
 
31
31
  # @!method example_description(node)
32
32
  def_node_matcher :example_description, <<~PATTERN
33
- (send _ {#Examples.all #ExampleGroups.all} ${
33
+ (send #rspec? {#Examples.all #ExampleGroups.all} ${
34
34
  $str
35
35
  $(dstr ({str dstr `sym} ...) ...)
36
36
  } ...)
@@ -5,11 +5,30 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks for consistent style of change matcher.
7
7
  #
8
- # Enforces either passing object and attribute as arguments to the matcher
9
- # or passing a block that reads the attribute value.
8
+ # Enforces either passing a receiver and message as method arguments,
9
+ # or a block.
10
10
  #
11
11
  # This cop can be configured using the `EnforcedStyle` option.
12
12
  #
13
+ # @safety
14
+ # Autocorrection is unsafe because `method_call` style calls the
15
+ # receiver *once* and sends the message to it before and after
16
+ # calling the `expect` block, whereas `block` style calls the
17
+ # expression *twice*, including the receiver.
18
+ #
19
+ # If your receiver is dynamic (e.g., the result of a method call) and
20
+ # you expect it to be called before and after the `expect` block,
21
+ # changing from `block` to `method_call` style may break your test.
22
+ #
23
+ # [source,ruby]
24
+ # ----
25
+ # expect { run }.to change { my_method.message }
26
+ # # `my_method` is called before and after `run`
27
+ #
28
+ # expect { run }.to change(my_method, :message)
29
+ # # `my_method` is called once, but `message` is called on it twice
30
+ # ----
31
+ #
13
32
  # @example `EnforcedStyle: method_call` (default)
14
33
  # # bad
15
34
  # expect { run }.to change { Foo.bar }
@@ -73,7 +73,7 @@ module RuboCop
73
73
  PATTERN
74
74
 
75
75
  def on_send(node)
76
- return if node.chained? || node.each_ancestor(:def, :defs).any?
76
+ return if node.chained? || node.each_ancestor(:any_def).any?
77
77
 
78
78
  if focused_block?(node)
79
79
  on_focused_block(node)
@@ -67,12 +67,12 @@ module RuboCop
67
67
 
68
68
  # @!method scoped_hook(node)
69
69
  def_node_matcher :scoped_hook, <<~PATTERN
70
- ({block numblock} $(send _ #Hooks.all (sym ${:each :example})) ...)
70
+ (any_block $(send _ #Hooks.all (sym ${:each :example})) ...)
71
71
  PATTERN
72
72
 
73
73
  # @!method unscoped_hook(node)
74
74
  def_node_matcher :unscoped_hook, <<~PATTERN
75
- ({block numblock} $(send _ #Hooks.all) ...)
75
+ (any_block $(send _ #Hooks.all) ...)
76
76
  PATTERN
77
77
 
78
78
  def on_block(node)
@@ -30,7 +30,7 @@ module RuboCop
30
30
  # @!method example_or_group?(node)
31
31
  def_node_matcher :example_or_group?, <<~PATTERN
32
32
  {
33
- ({block numblock} {
33
+ (any_block {
34
34
  (send #rspec? #ExampleGroups.all ...)
35
35
  (send nil? #Examples.all ...)
36
36
  } ...)
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for usage of `include_examples`.
7
+ #
8
+ # `include_examples`, unlike `it_behaves_like`, does not create its
9
+ # own context. As such, using `subject`, `let`, `before`/`after`, etc.
10
+ # within shared examples included with `include_examples` can have
11
+ # unexpected behavior and side effects.
12
+ #
13
+ # Prefer using `it_behaves_like` instead.
14
+ #
15
+ # @safety
16
+ # `include_examples` and `it_behaves_like` have different scoping
17
+ # behaviors.
18
+ # Changing `include_examples` to `it_behaves_like` creates a new
19
+ # context, altering setup dependencies, which can lead to unexpected
20
+ # test failures.
21
+ # Specifically, the scope of hooks (`before`, `after`, `around`)
22
+ # changes, which may prevent expected setup from being inherited
23
+ # correctly.
24
+ #
25
+ # Additionally, `let` and `subject` are affected by scoping rules.
26
+ # When `include_examples` is used, `let` and `subject` defined within
27
+ # `shared_examples` are evaluated in the caller's context, allowing
28
+ # access to their values.
29
+ # In contrast, `it_behaves_like` creates a new context, preventing
30
+ # access to `let` or `subject` values from the caller's context.
31
+ #
32
+ # [source,ruby]
33
+ # ----
34
+ # shared_examples "mock behavior" do
35
+ # before do
36
+ # allow(service).to receive(:call).and_return("mocked response")
37
+ # end
38
+ #
39
+ # it "returns mocked response" do
40
+ # expect(service.call).to eq "mocked response"
41
+ # end
42
+ # end
43
+ #
44
+ # context "working example with include_examples" do
45
+ # let(:service) { double(:service) }
46
+ #
47
+ # include_examples "mock behavior"
48
+ #
49
+ # it "uses the mocked service" do
50
+ # expect(service.call).to eq "mocked response" # Passes
51
+ # end
52
+ # end
53
+ #
54
+ # context "broken example with it_behaves_like" do
55
+ # let(:service) { double(:service) }
56
+ #
57
+ # it_behaves_like "mock behavior"
58
+ #
59
+ # it "unexpectedly does not use the mocked service" do
60
+ # # Fails because `it_behaves_like` does not apply the mock setup
61
+ # expect(service.call).to eq "mocked response"
62
+ # end
63
+ # end
64
+ # ----
65
+ #
66
+ # @example
67
+ # # bad
68
+ # include_examples 'examples'
69
+ #
70
+ # # good
71
+ # it_behaves_like 'examples'
72
+ #
73
+ class IncludeExamples < Base
74
+ extend AutoCorrector
75
+
76
+ MSG = 'Prefer `it_behaves_like` over `include_examples`.'
77
+
78
+ RESTRICT_ON_SEND = %i[include_examples].freeze
79
+
80
+ def on_send(node)
81
+ selector = node.loc.selector
82
+
83
+ add_offense(selector) do |corrector|
84
+ corrector.replace(selector, 'it_behaves_like')
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -17,6 +17,8 @@ module RuboCop
17
17
  # end
18
18
  #
19
19
  class IteratedExpectation < Base
20
+ extend AutoCorrector
21
+
20
22
  MSG = 'Prefer using the `all` matcher instead ' \
21
23
  'of iterating over an array.'
22
24
 
@@ -25,14 +27,14 @@ module RuboCop
25
27
  (block
26
28
  (send ... :each)
27
29
  (args (arg $_))
28
- $(...)
30
+ (...)
29
31
  )
30
32
  PATTERN
31
33
 
32
34
  # @!method each_numblock?(node)
33
35
  def_node_matcher :each_numblock?, <<~PATTERN
34
36
  (numblock
35
- (send ... :each) _ $(...)
37
+ (send ... :each) _ (...)
36
38
  )
37
39
  PATTERN
38
40
 
@@ -42,23 +44,43 @@ module RuboCop
42
44
  PATTERN
43
45
 
44
46
  def on_block(node)
45
- each?(node) do |arg, body|
46
- if single_expectation?(body, arg) || only_expectations?(body, arg)
47
- add_offense(node.send_node)
48
- end
47
+ each?(node) do |arg|
48
+ check_offense(node, arg)
49
49
  end
50
50
  end
51
51
 
52
52
  def on_numblock(node)
53
- each_numblock?(node) do |body|
54
- if single_expectation?(body, :_1) || only_expectations?(body, :_1)
55
- add_offense(node.send_node)
56
- end
53
+ each_numblock?(node) do
54
+ check_offense(node, :_1)
57
55
  end
58
56
  end
59
57
 
60
58
  private
61
59
 
60
+ def check_offense(node, argument)
61
+ if single_expectation?(node.body, argument)
62
+ add_offense(node.send_node) do |corrector|
63
+ next unless node.body.arguments.one?
64
+ next if uses_argument_in_matcher?(node, argument)
65
+
66
+ corrector.replace(node, single_expectation_replacement(node))
67
+ end
68
+ elsif only_expectations?(node.body, argument)
69
+ add_offense(node.send_node)
70
+ end
71
+ end
72
+
73
+ def single_expectation_replacement(node)
74
+ collection = node.receiver.source
75
+ matcher = node.body.first_argument.source
76
+
77
+ "expect(#{collection}).to all(#{matcher})"
78
+ end
79
+
80
+ def uses_argument_in_matcher?(node, argument)
81
+ node.body.first_argument.each_descendant.any?(s(:lvar, argument))
82
+ end
83
+
62
84
  def single_expectation?(body, arg)
63
85
  expectation?(body, arg)
64
86
  end
@@ -100,18 +100,21 @@ module RuboCop
100
100
 
101
101
  def on_casgn(node)
102
102
  return unless inside_describe_block?(node)
103
+ return if explicit_namespace?(node.namespace)
103
104
 
104
105
  add_offense(node, message: MSG_CONST)
105
106
  end
106
107
 
107
108
  def on_class(node)
108
109
  return unless inside_describe_block?(node)
110
+ return if explicit_namespace?(node.identifier.namespace)
109
111
 
110
112
  add_offense(node, message: MSG_CLASS)
111
113
  end
112
114
 
113
115
  def on_module(node)
114
116
  return unless inside_describe_block?(node)
117
+ return if explicit_namespace?(node.identifier.namespace)
115
118
 
116
119
  add_offense(node, message: MSG_MODULE)
117
120
  end
@@ -121,6 +124,10 @@ module RuboCop
121
124
  def inside_describe_block?(node)
122
125
  node.each_ancestor(:block).any? { |ancestor| spec_group?(ancestor) }
123
126
  end
127
+
128
+ def explicit_namespace?(namespace)
129
+ !namespace.nil?
130
+ end
124
131
  end
125
132
  end
126
133
  end
@@ -133,8 +133,8 @@ module RuboCop
133
133
 
134
134
  def count_up_nesting?(node, example_group)
135
135
  example_group &&
136
- (node.block_type? &&
137
- !allowed_groups.include?(node.method_name.to_s))
136
+ node.block_type? &&
137
+ !allowed_groups.include?(node.method_name.to_s)
138
138
  end
139
139
 
140
140
  def message(nesting)
@@ -65,7 +65,7 @@ module RuboCop
65
65
  # @param [RuboCop::AST::Node] node
66
66
  # @return [Boolean]
67
67
  def_node_matcher :regular_or_focused_example?, <<~PATTERN
68
- ({block numblock} (send nil? {#Examples.regular #Examples.focused} ...) ...)
68
+ (any_block (send nil? {#Examples.regular #Examples.focused} ...) ...)
69
69
  PATTERN
70
70
 
71
71
  # @!method includes_expectation?(node)
@@ -47,7 +47,7 @@ module RuboCop
47
47
 
48
48
  # @!method skippable_example?(node)
49
49
  def_node_matcher :skippable_example?, <<~PATTERN
50
- (send nil? #Examples.regular ...)
50
+ (send nil? #Examples.regular _ ...)
51
51
  PATTERN
52
52
 
53
53
  # @!method pending_block?(node)
@@ -63,8 +63,7 @@ module RuboCop
63
63
  def_node_matcher :skipped_in_example?, <<~PATTERN
64
64
  {
65
65
  (send nil? ${#Examples.skipped #Examples.pending})
66
- (block (send nil? ${#Examples.skipped}) ...)
67
- (numblock (send nil? ${#Examples.skipped}) ...)
66
+ (any_block (send nil? ${#Examples.skipped}) ...)
68
67
  }
69
68
  PATTERN
70
69
 
@@ -75,7 +74,7 @@ module RuboCop
75
74
 
76
75
  # @!method skipped_by_example_method_with_block?(node)
77
76
  def_node_matcher :skipped_by_example_method_with_block?, <<~PATTERN
78
- ({block numblock} (send nil? ${#Examples.skipped #Examples.pending} ...) ...)
77
+ (any_block (send nil? ${#Examples.skipped #Examples.pending} ...) ...)
79
78
  PATTERN
80
79
 
81
80
  # @!method metadata_without_reason?(node)
@@ -182,7 +182,7 @@ module RuboCop
182
182
 
183
183
  def heredoc_argument?(matcher)
184
184
  matcher.arguments.select do |arg|
185
- arg.str_type? || arg.dstr_type? || arg.xstr_type?
185
+ arg.type?(:str, :dstr, :xstr)
186
186
  end.any?(&:heredoc?)
187
187
  end
188
188
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks for multiple messages stubbed on the same object.
6
+ # Prefer `receive_messages` over multiple `receive`s on the same object.
7
7
  #
8
8
  # @safety
9
9
  # The autocorrection is marked as unsafe, because it may change the
@@ -148,8 +148,7 @@ module RuboCop
148
148
  end
149
149
 
150
150
  def heredoc_or_splat?(node)
151
- ((node.str_type? || node.dstr_type?) && node.heredoc?) ||
152
- node.splat_type?
151
+ (node.type?(:str, :dstr) && node.heredoc?) || node.splat_type?
153
152
  end
154
153
 
155
154
  def requires_quotes?(value)
@@ -41,7 +41,7 @@ module RuboCop
41
41
 
42
42
  # @!method match_redundant_around_hook_block?(node)
43
43
  def_node_matcher :match_redundant_around_hook_block?, <<~PATTERN
44
- ({block numblock} (send _ :around ...) ... (send _ :run))
44
+ (any_block (send _ :around ...) ... (send _ :run))
45
45
  PATTERN
46
46
 
47
47
  # @!method match_redundant_around_hook_send?(node)
@@ -5,7 +5,9 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks for setup scattered across multiple hooks in an example group.
7
7
  #
8
- # Unify `before`, `after`, and `around` hooks when possible.
8
+ # Unify `before` and `after` hooks when possible.
9
+ # However, `around` hooks are allowed to be defined multiple times,
10
+ # as unifying them would typically make the code harder to read.
9
11
  #
10
12
  # @example
11
13
  # # bad
@@ -22,6 +24,12 @@ module RuboCop
22
24
  # end
23
25
  # end
24
26
  #
27
+ # # good
28
+ # describe Foo do
29
+ # around { |example| before1; example.call; after1 }
30
+ # around { |example| before2; example.call; after2 }
31
+ # end
32
+ #
25
33
  class ScatteredSetup < Base
26
34
  include FinalEndLocation
27
35
  include RangeHelp
@@ -48,7 +56,7 @@ module RuboCop
48
56
  def repeated_hooks(node)
49
57
  hooks = RuboCop::RSpec::ExampleGroup.new(node)
50
58
  .hooks
51
- .select(&:knowable_scope?)
59
+ .select { |hook| hook.knowable_scope? && hook.name != :around }
52
60
  .group_by { |hook| [hook.name, hook.scope, hook.metadata] }
53
61
  .values
54
62
  .reject(&:one?)
@@ -69,7 +69,7 @@ module RuboCop
69
69
  end
70
70
 
71
71
  def symbol?(node)
72
- node.sym_type? || node.dsym_type?
72
+ node.type?(:sym, :dsym)
73
73
  end
74
74
  end
75
75
  end
@@ -50,7 +50,7 @@ module RuboCop
50
50
  return unless inside_example_group?(node)
51
51
 
52
52
  variable_definition?(node) do |variable|
53
- return if variable.dstr_type? || variable.dsym_type?
53
+ return if variable.type?(:dstr, :dsym)
54
54
  return if matches_allowed_pattern?(variable.value)
55
55
 
56
56
  check_name(node, variable.value, variable.source_range)
@@ -48,6 +48,7 @@ require_relative 'rspec/identical_equality_assertion'
48
48
  require_relative 'rspec/implicit_block_expectation'
49
49
  require_relative 'rspec/implicit_expect'
50
50
  require_relative 'rspec/implicit_subject'
51
+ require_relative 'rspec/include_examples'
51
52
  require_relative 'rspec/indexed_let'
52
53
  require_relative 'rspec/instance_spy'
53
54
  require_relative 'rspec/instance_variable'
@@ -64,7 +64,9 @@ module RuboCop
64
64
  end
65
65
 
66
66
  def transform_true(node)
67
- node.true_type? ? true : node
67
+ return true if node.true_type?
68
+
69
+ node
68
70
  end
69
71
 
70
72
  def scope_name
@@ -26,7 +26,7 @@ module RuboCop
26
26
 
27
27
  # @!method example_group?(node)
28
28
  def_node_matcher :example_group?, <<~PATTERN
29
- ({block numblock} (send #rspec? #ExampleGroups.all ...) ...)
29
+ (any_block (send #rspec? #ExampleGroups.all ...) ...)
30
30
  PATTERN
31
31
 
32
32
  # @!method shared_group?(node)
@@ -35,7 +35,7 @@ module RuboCop
35
35
 
36
36
  # @!method spec_group?(node)
37
37
  def_node_matcher :spec_group?, <<~PATTERN
38
- ({block numblock} (send #rspec?
38
+ (any_block (send #rspec?
39
39
  {#SharedGroups.all #ExampleGroups.all}
40
40
  ...) ...)
41
41
  PATTERN
@@ -50,10 +50,7 @@ module RuboCop
50
50
 
51
51
  # @!method hook?(node)
52
52
  def_node_matcher :hook?, <<~PATTERN
53
- {
54
- (numblock (send nil? #Hooks.all ...) ...)
55
- (block (send nil? #Hooks.all ...) ...)
56
- }
53
+ (any_block (send nil? #Hooks.all ...) ...)
57
54
  PATTERN
58
55
 
59
56
  # @!method let?(node)
@@ -75,6 +72,12 @@ module RuboCop
75
72
  # @!method subject?(node)
76
73
  def_node_matcher :subject?, '(block (send nil? #Subjects.all ...) ...)'
77
74
 
75
+ module ErrorMatchers # :nodoc:
76
+ def self.all(element)
77
+ Language.config['ErrorMatchers'].include?(element.to_s)
78
+ end
79
+ end
80
+
78
81
  module ExampleGroups # :nodoc:
79
82
  class << self
80
83
  def all(element)
@@ -203,14 +206,14 @@ module RuboCop
203
206
  # This is used in Dialect and DescribeClass cops to detect RSpec blocks.
204
207
  module ALL # :nodoc:
205
208
  def self.all(element)
206
- [ExampleGroups, Examples, Expectations, Helpers, Hooks, Includes,
207
- Runners, SharedGroups, Subjects]
209
+ [ErrorMatchers, ExampleGroups, Examples, Expectations, Helpers, Hooks,
210
+ Includes, Runners, SharedGroups, Subjects]
208
211
  .find { |concept| concept.all(element) }
209
212
  end
210
213
  end
211
214
 
212
- private_constant :ExampleGroups, :Examples, :Expectations, :Hooks,
213
- :Includes, :Runners, :SharedGroups, :ALL
215
+ private_constant :ErrorMatchers, :ExampleGroups, :Examples, :Expectations,
216
+ :Hooks, :Includes, :Runners, :SharedGroups, :ALL
214
217
  end
215
218
  end
216
219
  end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Version information for the RSpec RuboCop plugin.
6
6
  module Version
7
- STRING = '3.5.0'
7
+ STRING = '3.7.0'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -9,7 +9,7 @@ authors:
9
9
  - Nils Gemeinhardt
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-02-16 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: lint_roller
@@ -114,6 +114,7 @@ files:
114
114
  - lib/rubocop/cop/rspec/implicit_block_expectation.rb
115
115
  - lib/rubocop/cop/rspec/implicit_expect.rb
116
116
  - lib/rubocop/cop/rspec/implicit_subject.rb
117
+ - lib/rubocop/cop/rspec/include_examples.rb
117
118
  - lib/rubocop/cop/rspec/indexed_let.rb
118
119
  - lib/rubocop/cop/rspec/instance_spy.rb
119
120
  - lib/rubocop/cop/rspec/instance_variable.rb
@@ -226,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
227
  - !ruby/object:Gem::Version
227
228
  version: '0'
228
229
  requirements: []
229
- rubygems_version: 3.6.2
230
+ rubygems_version: 3.6.9
230
231
  specification_version: 4
231
232
  summary: Code style checking for RSpec files
232
233
  test_files: []