rubocop-rspec 2.22.0 → 2.23.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e786386e218833ac7f78af9ffcadadb6bf6c822a31b455cac1567152fd3bc9c0
4
- data.tar.gz: 384170b4ae3a06c05b30855ae9361057c53226b40b924d4ec1d49c0d15195658
3
+ metadata.gz: e0eebbfb772c1598b0ee4cfe88c74d8b81171e197dbd4d5dc6020df95005440e
4
+ data.tar.gz: 2936be502e0062b94a0346bd98696194fd33fe6f7693f8c92bc38a952ee31b26
5
5
  SHA512:
6
- metadata.gz: 554345811f404b27a39fd522e0085022c39147e0487f04ea77ed7887002451af112e2ddc45ad94841c0a4201a76f37fbfb7a2e64aed3c7efa91cddf1a30843bc
7
- data.tar.gz: 22882a56ddf03c55d6eaf299fb05197c835049054231e6d7798e2ca3eac5cbd9840eeada823a12431b00efd37289a8353792dfe3c8f653b2606f9ccda480615a
6
+ metadata.gz: 14e3e53093b7e104ab212217fdc8226a909fba9bdd84fa44c22963cf8e62d17ce858a2180b8b5b3727b5dd2a74a9b4c6e2139d8d61cbc6595b2c46f9022214af
7
+ data.tar.gz: 9408c50d35ae50e1825fef43f2c6a6def0d87e44bfbbd968bb19996b1a867f215df36ab62d451af892e6855c4330c0d4fb9d09b8400ff5f35b4ecef1cfb86adb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 2.23.0 (2023-07-30)
6
+
7
+ - Add new `RSpec/Rails/NegationBeValid` cop. ([@ydah])
8
+ - Fix a false negative for `RSpec/ExcessiveDocstringSpacing` when finds description with em space. ([@ydah])
9
+ - Fix a false positive for `RSpec/EmptyExampleGroup` when example group with examples defined in `if` branch inside iterator. ([@ydah])
10
+ - Update the message output of `RSpec/ExpectActual` to include the word 'value'. ([@corydiamand])
11
+ - Fix a false negative for `RSpec/Pending` when `it` without body. ([@ydah])
12
+ - Add new `RSpec/ReceiveMessages` cop. ([@ydah])
13
+ - Change default.yml path to use `**/spec/*` instead of `spec/*`. ([@ydah])
14
+ - Add `AllowedIdentifiers` and `AllowedPatterns` configuration option to `RSpec/IndexedLet`. ([@ydah])
15
+ - Fix `RSpec/NamedSubject` when block has no body. ([@splattael])
16
+ - Fix `RSpec/LetBeforeExamples` autocorrect incompatible with `RSpec/ScatteredLet` autocorrect. ([@ydah])
17
+ - Update `RSpec/Focus` to support `shared_context` and `shared_examples` ([@tmaier])
18
+
5
19
  ## 2.22.0 (2023-05-06)
6
20
 
7
21
  - Extract factory_bot cops to a separate repository, [`rubocop-factory_bot`](https://github.com/rubocop/rubocop-factory_bot). The `rubocop-factory_bot` repository is a dependency of `rubocop-rspec` and the factory_bot cops are aliased (`RSpec/FactoryBot/Foo` == `FactoryBot/Foo`) until v3.0 is released, so the change will be invisible to users until then. ([@ydah])
@@ -781,6 +795,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
781
795
  [@cfabianski]: https://github.com/cfabianski
782
796
  [@clupprich]: https://github.com/clupprich
783
797
  [@composerinteralia]: https://github.com/composerinteralia
798
+ [@corydiamand]: https://github.com/corydiamand
784
799
  [@darhazer]: https://github.com/Darhazer
785
800
  [@daveworth]: https://github.com/daveworth
786
801
  [@dduugg]: https://github.com/dduugg
@@ -859,6 +874,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
859
874
  [@seanpdoyle]: https://github.com/seanpdoyle
860
875
  [@sl4vr]: https://github.com/sl4vr
861
876
  [@smcgivern]: https://github.com/smcgivern
877
+ [@splattael]: https://github.com/splattael
862
878
  [@stephannv]: https://github.com/stephannv
863
879
  [@t3h2mas]: https://github.com/t3h2mas
864
880
  [@tdeo]: https://github.com/tdeo
@@ -866,6 +882,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
866
882
  [@telmofcosta]: https://github.com/telmofcosta
867
883
  [@tietew]: https://github.com/Tietew
868
884
  [@timrogers]: https://github.com/timrogers
885
+ [@tmaier]: https://github.com/tmaier
869
886
  [@topalovic]: https://github.com/topalovic
870
887
  [@twalpole]: https://github.com/twalpole
871
888
  [@vzvu3k6k]: https://github.com/vzvu3k6k
data/README.md CHANGED
@@ -17,7 +17,7 @@ gem install rubocop-rspec
17
17
 
18
18
  or if you use bundler put this in your `Gemfile`
19
19
 
20
- ```
20
+ ```ruby
21
21
  gem 'rubocop-rspec', require: false
22
22
  ```
23
23
 
data/config/default.yml CHANGED
@@ -181,10 +181,11 @@ RSpec/BeforeAfterAll:
181
181
  Description: Check that before/after(:all) isn't being used.
182
182
  Enabled: true
183
183
  Exclude:
184
- - spec/spec_helper.rb
185
- - spec/rails_helper.rb
186
- - spec/support/**/*.rb
184
+ - "**/spec/spec_helper.rb"
185
+ - "**/spec/rails_helper.rb"
186
+ - "**/spec/support/**/*.rb"
187
187
  VersionAdded: '1.12'
188
+ VersionChanged: '2.23'
188
189
  StyleGuide: https://rspec.rubystyle.guide/#avoid-hooks-with-context-scope
189
190
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeforeAfterAll
190
191
 
@@ -404,8 +405,9 @@ RSpec/ExpectActual:
404
405
  Description: Checks for `expect(...)` calls containing literal values.
405
406
  Enabled: true
406
407
  Exclude:
407
- - spec/routing/**/*
408
+ - "**/spec/routing/**/*"
408
409
  VersionAdded: '1.7'
410
+ VersionChanged: '2.23'
409
411
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExpectActual
410
412
 
411
413
  RSpec/ExpectChange:
@@ -513,8 +515,11 @@ RSpec/IndexedLet:
513
515
  Description: Do not set up test data using indexes (e.g., `item_1`, `item_2`).
514
516
  Enabled: pending
515
517
  VersionAdded: '2.20'
518
+ VersionChanged: '2.23'
516
519
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IndexedLet
517
520
  Max: 1
521
+ AllowedIdentifiers: []
522
+ AllowedPatterns: []
518
523
 
519
524
  RSpec/InstanceSpy:
520
525
  Description: Checks for `instance_double` used with `have_received`.
@@ -725,6 +730,12 @@ RSpec/ReceiveCounts:
725
730
  VersionAdded: '1.26'
726
731
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveCounts
727
732
 
733
+ RSpec/ReceiveMessages:
734
+ Description: Checks for multiple messages stubbed on the same object.
735
+ Enabled: pending
736
+ VersionAdded: '2.23'
737
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ReceiveMessages
738
+
728
739
  RSpec/ReceiveNever:
729
740
  Description: Prefer `not_to receive(...)` over `receive(...).never`.
730
741
  Enabled: true
@@ -974,11 +985,11 @@ RSpec/FactoryBot/AttributeDefinedStatically:
974
985
  Description: Always declare attribute values as blocks.
975
986
  Enabled: true
976
987
  Include:
977
- - spec/factories.rb
978
- - spec/factories/**/*.rb
979
- - features/support/factories/**/*.rb
988
+ - "**/spec/factories.rb"
989
+ - "**/spec/factories/**/*.rb"
990
+ - "**/features/support/factories/**/*.rb"
980
991
  VersionAdded: '1.28'
981
- VersionChanged: '2.0'
992
+ VersionChanged: '2.23'
982
993
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
983
994
 
984
995
  RSpec/FactoryBot/ConsistentParenthesesStyle:
@@ -997,26 +1008,26 @@ RSpec/FactoryBot/CreateList:
997
1008
  Include:
998
1009
  - "**/*_spec.rb"
999
1010
  - "**/spec/**/*"
1000
- - spec/factories.rb
1001
- - spec/factories/**/*.rb
1002
- - features/support/factories/**/*.rb
1011
+ - "**/spec/factories.rb"
1012
+ - "**/spec/factories/**/*.rb"
1013
+ - "**/features/support/factories/**/*.rb"
1003
1014
  EnforcedStyle: create_list
1004
1015
  SupportedStyles:
1005
1016
  - create_list
1006
1017
  - n_times
1007
1018
  VersionAdded: '1.25'
1008
- VersionChanged: '2.0'
1019
+ VersionChanged: '2.23'
1009
1020
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/CreateList
1010
1021
 
1011
1022
  RSpec/FactoryBot/FactoryClassName:
1012
1023
  Description: Use string value when setting the class attribute explicitly.
1013
1024
  Enabled: true
1014
1025
  Include:
1015
- - spec/factories.rb
1016
- - spec/factories/**/*.rb
1017
- - features/support/factories/**/*.rb
1026
+ - "**/spec/factories.rb"
1027
+ - "**/spec/factories/**/*.rb"
1028
+ - "**/features/support/factories/**/*.rb"
1018
1029
  VersionAdded: '1.37'
1019
- VersionChanged: '2.0'
1030
+ VersionChanged: '2.23'
1020
1031
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/FactoryClassName
1021
1032
 
1022
1033
  RSpec/FactoryBot/FactoryNameStyle:
@@ -1095,6 +1106,16 @@ RSpec/Rails/MinitestAssertions:
1095
1106
  VersionAdded: '2.17'
1096
1107
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/MinitestAssertions
1097
1108
 
1109
+ RSpec/Rails/NegationBeValid:
1110
+ Description: Enforces use of `be_invalid` or `not_to` for negated be_valid.
1111
+ EnforcedStyle: not_to
1112
+ SupportedStyles:
1113
+ - not_to
1114
+ - be_invalid
1115
+ Enabled: pending
1116
+ VersionAdded: '2.23'
1117
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/NegationBeValid
1118
+
1098
1119
  RSpec/Rails/TravelAround:
1099
1120
  Description: Prefer to travel in `before` rather than `around`.
1100
1121
  Enabled: pending
@@ -131,6 +131,7 @@ module RuboCop
131
131
  {
132
132
  #examples_directly_or_in_block?
133
133
  (begin <#examples_directly_or_in_block? ...>)
134
+ (begin <#examples_in_branches? ...>)
134
135
  }
135
136
  PATTERN
136
137
 
@@ -169,6 +170,8 @@ module RuboCop
169
170
  end
170
171
 
171
172
  def examples_in_branches?(condition_node)
173
+ return if !condition_node.if_type? && !condition_node.case_type?
174
+
172
175
  condition_node.branches.any? { |branch| examples?(branch) }
173
176
  end
174
177
 
@@ -52,14 +52,21 @@ module RuboCop
52
52
 
53
53
  # @param text [String]
54
54
  def excessive_whitespace?(text)
55
- return true if text.start_with?(' ') || text.end_with?(' ')
56
-
57
- text.match?(/[^\n ] +[^ ]/)
55
+ text.match?(/
56
+ # Leading space
57
+ \A[[:blank:]]
58
+ |
59
+ # Trailing space
60
+ [[:blank:]]\z
61
+ |
62
+ # Two or more consecutive spaces, except if they are leading spaces
63
+ [^[[:space:]]][[:blank:]]{2,}[^[[:blank:]]]
64
+ /x)
58
65
  end
59
66
 
60
67
  # @param text [String]
61
68
  def strip_excessive_whitespace(text)
62
- text.strip.gsub(/ +/, ' ')
69
+ text.strip.gsub(/[[:blank:]]{2,}/, ' ')
63
70
  end
64
71
 
65
72
  # @param node [RuboCop::AST::Node]
@@ -24,7 +24,7 @@ module RuboCop
24
24
  class ExpectActual < Base
25
25
  extend AutoCorrector
26
26
 
27
- MSG = 'Provide the actual you are testing to `expect(...)`.'
27
+ MSG = 'Provide the actual value you are testing to `expect(...)`.'
28
28
 
29
29
  RESTRICT_ON_SEND = Runners.all
30
30
 
@@ -77,7 +77,7 @@ module RuboCop
77
77
 
78
78
  private
79
79
 
80
- # This is not implement using a NodePattern because it seems
80
+ # This is not implemented using a NodePattern because it seems
81
81
  # to not be able to match against an explicit (nil) sexp
82
82
  def literal?(node)
83
83
  node && (simple_literal?(node) || complex_literal?(node))
@@ -34,6 +34,18 @@ module RuboCop
34
34
  # # good
35
35
  # describe 'test' do; end
36
36
  #
37
+ # # bad
38
+ # shared_examples 'test', focus: true do; end
39
+ #
40
+ # # good
41
+ # shared_examples 'test' do; end
42
+ #
43
+ # # bad
44
+ # shared_context 'test', focus: true do; end
45
+ #
46
+ # # good
47
+ # shared_context 'test' do; end
48
+ #
37
49
  # # bad (does not support autocorrection)
38
50
  # focus 'test' do; end
39
51
  #
@@ -51,6 +63,7 @@ module RuboCop
51
63
  #Examples.regular
52
64
  #Examples.skipped
53
65
  #Examples.pending
66
+ #SharedGroups.all
54
67
  }
55
68
  PATTERN
56
69
 
@@ -8,6 +8,9 @@ module RuboCop
8
8
  # It makes reading the test harder because it's not clear what exactly
9
9
  # is tested by this particular example.
10
10
  #
11
+ # The configurable options `AllowedIdentifiers` and `AllowedPatterns`
12
+ # will also read those set in `Naming/VariableNumber`.
13
+ #
11
14
  # @example `Max: 1 (default)`
12
15
  # # bad
13
16
  # let(:item_1) { create(:item) }
@@ -31,7 +34,20 @@ module RuboCop
31
34
  # let(:item_1) { create(:item) }
32
35
  # let(:item_2) { create(:item) }
33
36
  #
37
+ # @example `AllowedIdentifiers: ['item_1', 'item_2']`
38
+ # # good
39
+ # let(:item_1) { create(:item) }
40
+ # let(:item_2) { create(:item) }
41
+ #
42
+ # @example `AllowedPatterns: ['item']`
43
+ # # good
44
+ # let(:item_1) { create(:item) }
45
+ # let(:item_2) { create(:item) }
46
+ #
34
47
  class IndexedLet < Base
48
+ include AllowedIdentifiers
49
+ include AllowedPattern
50
+
35
51
  MSG = 'This `let` statement uses index in its name. Please give it ' \
36
52
  'a meaningful name.'
37
53
 
@@ -69,12 +85,27 @@ module RuboCop
69
85
  end
70
86
 
71
87
  def indexed_let?(node)
72
- let?(node) && SUFFIX_INDEX_REGEX.match?(let_name(node))
88
+ let?(node) &&
89
+ SUFFIX_INDEX_REGEX.match?(let_name(node)) &&
90
+ !allowed_identifier?(let_name(node).to_s) &&
91
+ !matches_allowed_pattern?(let_name(node).to_s)
73
92
  end
74
93
 
75
94
  def let_name_stripped_index(node)
76
95
  let_name(node).to_s.gsub(INDEX_REGEX, '')
77
96
  end
97
+
98
+ def cop_config_patterns_values
99
+ Array(config.for_cop('Naming/VariableNumber')
100
+ .fetch('AllowedPatterns', [])) +
101
+ Array(cop_config.fetch('AllowedPatterns', []))
102
+ end
103
+
104
+ def allowed_identifiers
105
+ Array(config.for_cop('Naming/VariableNumber')
106
+ .fetch('AllowedIdentifiers', [])) +
107
+ Array(cop_config.fetch('AllowedIdentifiers', []))
108
+ end
78
109
  end
79
110
  end
80
111
  end
@@ -48,7 +48,7 @@ module RuboCop
48
48
  class InstanceVariable < Base
49
49
  include TopLevelGroup
50
50
 
51
- MSG = 'Avoid instance variables use let, ' \
51
+ MSG = 'Avoid instance variables - use let, ' \
52
52
  'a method call, or a local variable (if possible).'
53
53
 
54
54
  # @!method dynamic_class?(node)
@@ -17,7 +17,7 @@ module RuboCop
17
17
  # Anonymous classes are fine, since they don't result in global
18
18
  # namespace name clashes.
19
19
  #
20
- # @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants
20
+ # @see https://rspec.info/features/3-12/rspec-mocks/mutating-constants
21
21
  #
22
22
  # @example Constants leak between examples
23
23
  # # bad
@@ -51,6 +51,10 @@ module RuboCop
51
51
  }
52
52
  PATTERN
53
53
 
54
+ def self.autocorrect_incompatible_with
55
+ [RSpec::ScatteredLet]
56
+ end
57
+
54
58
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
55
59
  return unless example_group_with_body?(node)
56
60
 
@@ -145,7 +145,7 @@ module RuboCop
145
145
  end
146
146
 
147
147
  def find_subject(block_node)
148
- block_node.body.child_nodes.find { |send_node| subject?(send_node) }
148
+ block_node.body&.child_nodes&.find { |send_node| subject?(send_node) }
149
149
  end
150
150
  end
151
151
  end
@@ -41,10 +41,15 @@ module RuboCop
41
41
  def_node_matcher :skippable?, <<~PATTERN
42
42
  {
43
43
  (send #rspec? #ExampleGroups.regular ...)
44
- (send nil? #Examples.regular ...)
44
+ #skippable_example?
45
45
  }
46
46
  PATTERN
47
47
 
48
+ # @!method skippable_example?(node)
49
+ def_node_matcher :skippable_example?, <<~PATTERN
50
+ (send nil? #Examples.regular ...)
51
+ PATTERN
52
+
48
53
  # @!method pending_block?(node)
49
54
  def_node_matcher :pending_block?, <<~PATTERN
50
55
  {
@@ -62,7 +67,12 @@ module RuboCop
62
67
  private
63
68
 
64
69
  def skipped?(node)
65
- skippable?(node) && skipped_in_metadata?(node)
70
+ skippable?(node) && skipped_in_metadata?(node) ||
71
+ skipped_regular_example_without_body?(node)
72
+ end
73
+
74
+ def skipped_regular_example_without_body?(node)
75
+ skippable_example?(node) && !node.block_node
66
76
  end
67
77
  end
68
78
  end
@@ -74,7 +74,7 @@ module RuboCop
74
74
  name[0..-2]
75
75
  when 'exist?', 'exists?'
76
76
  'exist'
77
- when /^has_/
77
+ when /\Ahas_/
78
78
  name.sub('has_', 'have_')[0..-2]
79
79
  else
80
80
  "be_#{name[0..-2]}"
@@ -240,10 +240,10 @@ module RuboCop
240
240
  'include?'
241
241
  when 'respond_to'
242
242
  'respond_to?'
243
- when /^have_(.+)/
243
+ when /\Ahave_(.+)/
244
244
  "has_#{Regexp.last_match(1)}?"
245
245
  else
246
- "#{matcher[/^be_(.+)/, 1]}?"
246
+ "#{matcher[/\Abe_(.+)/, 1]}?"
247
247
  end
248
248
  end
249
249
  # rubocop:enable Metrics/MethodLength
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Rails
7
+ # Enforces use of `be_invalid` or `not_to` for negated be_valid.
8
+ #
9
+ # @example EnforcedStyle: not_to (default)
10
+ # # bad
11
+ # expect(foo).to be_invalid
12
+ #
13
+ # # good
14
+ # expect(foo).not_to be_valid
15
+ #
16
+ # @example EnforcedStyle: be_invalid
17
+ # # bad
18
+ # expect(foo).not_to be_valid
19
+ #
20
+ # # good
21
+ # expect(foo).to be_invalid
22
+ #
23
+ class NegationBeValid < Base
24
+ extend AutoCorrector
25
+ include ConfigurableEnforcedStyle
26
+
27
+ MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
28
+ RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze
29
+
30
+ # @!method not_to?(node)
31
+ def_node_matcher :not_to?, <<~PATTERN
32
+ (send ... :not_to (send nil? :be_valid ...))
33
+ PATTERN
34
+
35
+ # @!method be_invalid?(node)
36
+ def_node_matcher :be_invalid?, <<~PATTERN
37
+ (send ... :to (send nil? :be_invalid ...))
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ return unless offense?(node.parent)
42
+
43
+ add_offense(offense_range(node),
44
+ message: message(node.method_name)) do |corrector|
45
+ corrector.replace(node.parent.loc.selector, replaced_runner)
46
+ corrector.replace(node.loc.selector, replaced_matcher)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def offense?(node)
53
+ case style
54
+ when :not_to
55
+ be_invalid?(node)
56
+ when :be_invalid
57
+ not_to?(node)
58
+ end
59
+ end
60
+
61
+ def offense_range(node)
62
+ node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
63
+ end
64
+
65
+ def message(_matcher)
66
+ format(MSG,
67
+ runner: replaced_runner,
68
+ matcher: replaced_matcher)
69
+ end
70
+
71
+ def replaced_runner
72
+ case style
73
+ when :not_to
74
+ 'not_to'
75
+ when :be_invalid
76
+ 'to'
77
+ end
78
+ end
79
+
80
+ def replaced_matcher
81
+ case style
82
+ when :not_to
83
+ 'be_valid'
84
+ when :be_invalid
85
+ 'be_invalid'
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for multiple messages stubbed on the same object.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # before do
11
+ # allow(Service).to receive(:foo).and_return(bar)
12
+ # allow(Service).to receive(:baz).and_return(qux)
13
+ # end
14
+ #
15
+ # # good
16
+ # before do
17
+ # allow(Service).to receive_messages(foo: bar, baz: qux)
18
+ # end
19
+ #
20
+ # # good - ignore same message
21
+ # before do
22
+ # allow(Service).to receive(:foo).and_return(bar)
23
+ # allow(Service).to receive(:foo).and_return(qux)
24
+ # end
25
+ #
26
+ class ReceiveMessages < Base
27
+ extend AutoCorrector
28
+ include RangeHelp
29
+
30
+ MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
31
+ '%<loc>s.'
32
+
33
+ # @!method allow_receive_message?(node)
34
+ def_node_matcher :allow_receive_message?, <<~PATTERN
35
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc?))
36
+ PATTERN
37
+
38
+ # @!method allow_argument(node)
39
+ def_node_matcher :allow_argument, <<~PATTERN
40
+ (send (send nil? :allow $_ ...) ...)
41
+ PATTERN
42
+
43
+ # @!method receive_node(node)
44
+ def_node_search :receive_node, <<~PATTERN
45
+ $(send (send nil? :receive ...) ...)
46
+ PATTERN
47
+
48
+ # @!method receive_arg(node)
49
+ def_node_search :receive_arg, <<~PATTERN
50
+ (send (send nil? :receive $_) ...)
51
+ PATTERN
52
+
53
+ # @!method receive_and_return_argument(node)
54
+ def_node_matcher :receive_and_return_argument, <<~PATTERN
55
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
56
+ PATTERN
57
+
58
+ def on_begin(node)
59
+ repeated_receive_message(node).each do |item, repeated_lines, args|
60
+ next if repeated_lines.empty?
61
+
62
+ register_offense(item, repeated_lines, args)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def repeated_receive_message(node)
69
+ node
70
+ .children
71
+ .select { |child| allow_receive_message?(child) }
72
+ .group_by { |child| allow_argument(child) }
73
+ .values
74
+ .reject(&:one?)
75
+ .flat_map { |items| add_repeated_lines_and_arguments(items) }
76
+ end
77
+
78
+ def add_repeated_lines_and_arguments(items)
79
+ uniq_items = uniq_items(items)
80
+ repeated_lines = uniq_items.map(&:first_line)
81
+ uniq_items.map do |item|
82
+ [item, repeated_lines - [item.first_line], arguments(uniq_items)]
83
+ end
84
+ end
85
+
86
+ def uniq_items(items)
87
+ items.select do |item|
88
+ items.none? do |i|
89
+ receive_arg(item).first == receive_arg(i).first &&
90
+ !same_line?(item, i)
91
+ end
92
+ end
93
+ end
94
+
95
+ def arguments(items)
96
+ items.map do |item|
97
+ receive_and_return_argument(item) do |receive_arg, return_arg|
98
+ "#{normalize_receive_arg(receive_arg)}: " \
99
+ "#{normalize_return_arg(return_arg)}"
100
+ end
101
+ end
102
+ end
103
+
104
+ def normalize_receive_arg(receive_arg)
105
+ if requires_quotes?(receive_arg)
106
+ "'#{receive_arg}'"
107
+ else
108
+ receive_arg
109
+ end
110
+ end
111
+
112
+ def normalize_return_arg(return_arg)
113
+ if return_arg.hash_type? && !return_arg.braces?
114
+ "{ #{return_arg.source} }"
115
+ else
116
+ return_arg.source
117
+ end
118
+ end
119
+
120
+ def register_offense(item, repeated_lines, args)
121
+ add_offense(item, message: message(repeated_lines)) do |corrector|
122
+ if item.loc.line < repeated_lines.min
123
+ replace_to_receive_messages(corrector, item, args)
124
+ else
125
+ corrector.remove(item_range_by_whole_lines(item))
126
+ end
127
+ end
128
+ end
129
+
130
+ def message(repeated_lines)
131
+ format(MSG, loc: repeated_lines)
132
+ end
133
+
134
+ def replace_to_receive_messages(corrector, item, args)
135
+ receive_node(item) do |node|
136
+ corrector.replace(node,
137
+ "receive_messages(#{args.join(', ')})")
138
+ end
139
+ end
140
+
141
+ def item_range_by_whole_lines(item)
142
+ range_by_whole_lines(item.source_range, include_final_newline: true)
143
+ end
144
+
145
+ def heredoc?(node)
146
+ (node.str_type? || node.dstr_type?) && node.heredoc?
147
+ end
148
+
149
+ def requires_quotes?(value)
150
+ value.match?(/^:".*?"|=$/)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  #
8
8
  # Only investigates references that are one of the supported styles.
9
9
  #
10
- # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
10
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
11
11
  #
12
12
  # This cop can be configured in your configuration using the
13
13
  # `EnforcedStyle` option and supports `--auto-gen-config`.
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module RSpec
6
6
  # Prefer using verifying doubles over normal doubles.
7
7
  #
8
- # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
8
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
9
9
  #
10
10
  # @example
11
11
  # # bad
@@ -18,6 +18,7 @@ require_relative 'rspec/factory_bot/syntax_methods'
18
18
 
19
19
  require_relative 'rspec/rails/avoid_setup_hook'
20
20
  require_relative 'rspec/rails/have_http_status'
21
+ require_relative 'rspec/rails/negation_be_valid'
21
22
  begin
22
23
  require_relative 'rspec/rails/http_status'
23
24
  rescue LoadError
@@ -99,6 +100,7 @@ require_relative 'rspec/pending'
99
100
  require_relative 'rspec/pending_without_reason'
100
101
  require_relative 'rspec/predicate_matcher'
101
102
  require_relative 'rspec/receive_counts'
103
+ require_relative 'rspec/receive_messages'
102
104
  require_relative 'rspec/receive_never'
103
105
  require_relative 'rspec/redundant_around'
104
106
  require_relative 'rspec/repeated_description'
@@ -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 = '2.22.0'
7
+ STRING = '2.23.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: 2.22.0
4
+ version: 2.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-05-06 00:00:00.000000000 Z
13
+ date: 2023-07-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -175,8 +175,10 @@ files:
175
175
  - lib/rubocop/cop/rspec/rails/http_status.rb
176
176
  - lib/rubocop/cop/rspec/rails/inferred_spec_type.rb
177
177
  - lib/rubocop/cop/rspec/rails/minitest_assertions.rb
178
+ - lib/rubocop/cop/rspec/rails/negation_be_valid.rb
178
179
  - lib/rubocop/cop/rspec/rails/travel_around.rb
179
180
  - lib/rubocop/cop/rspec/receive_counts.rb
181
+ - lib/rubocop/cop/rspec/receive_messages.rb
180
182
  - lib/rubocop/cop/rspec/receive_never.rb
181
183
  - lib/rubocop/cop/rspec/redundant_around.rb
182
184
  - lib/rubocop/cop/rspec/repeated_description.rb