rubocop-rspec 2.19.0 → 2.21.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: 44e3a236fcd4869222a8e50780c5ef50671968fe82661b22f34d93315116c36b
4
- data.tar.gz: 22c11a55d5d29ae10f2ec8fc4a3c95a3bd7f2b1bb5a0b049a9e47d3328178e7d
3
+ metadata.gz: 5a388b40471f85e4da35358c9c9c0ab8bdce272c171b989553a174ea5f2fa04f
4
+ data.tar.gz: 251c7b2c7f83daeb4f3e3f4e10f1b0f37763088ac792982e27052da26c1645ea
5
5
  SHA512:
6
- metadata.gz: bbfab487662a025737c213ab0f9c0a4c54d1d93fe3a3c05a344b20dcd2f83900aef723f07d0ecdade3c616afa15bee4a6cb975345ea9d8a80e3ba0a64c715a05
7
- data.tar.gz: a1f112ce509e7a938a7d0377ac2fcf0a5c723fbac2299f5246ab84b4d989d8223b7a608c5952368094515085d4a106695d3baf71b5f8b02769cdbbf4965d9cd4
6
+ metadata.gz: 6311a48d125da92639f8bebbeebdd9b16e7968bf2b522d29310c913490977a465aa77112e39497880373be23732a2fb3cfa205b92475e2edd069c1009ae91aac
7
+ data.tar.gz: fd3d495d5ec63969e2d926c5c151cf888de321b74486644983c6aa72b3c18b071daafe19b0996a0fa476334d00bf05768c09ae4ce03d39bbdf436898b64809c0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 2.21.0 (2023-05-05)
6
+
7
+ - Fix a false positive in `RSpec/IndexedLet` with suffixes after index-like numbers. ([@pirj])
8
+ - Fix an error for `RSpec/Rails/HaveHttpStatus` with comparison with strings containing non-numeric characters. ([@ydah])
9
+ - Fix an error for `RSpec/MatchArray` when `match_array` with no argument. ([@ydah])
10
+ - Add support `a_block_changing` and `changing` for `RSpec/ChangeByZero`. ([@ydah])
11
+ - Drop Ruby 2.6 support. ([@ydah])
12
+
13
+ ## 2.20.0 (2023-04-18)
14
+
15
+ - Add new `RSpec/IndexedLet` cop. ([@dmitrytsepelev])
16
+ - Add new `RSpec/BeEmpty` cop. ([@ydah], [@bquorning])
17
+ - Add autocorrect support for `RSpec/ScatteredSetup`. ([@ydah])
18
+ - Add support `be_status` style for `RSpec/Rails/HttpStatus`. ([@ydah])
19
+ - Add support for shared example groups to `RSpec/EmptyLineAfterExampleGroup`. ([@pirj])
20
+ - Add support for `RSpec/HaveHttpStatus` when using `response.code`. ([@ydah])
21
+ - Fix order of expected and actual in correction for `RSpec/Rails/MinitestAssertions` ([@mvz])
22
+ - Fix a false positive for `RSpec/DescribedClassModuleWrapping` when RSpec.describe numblock is nested within a module. ([@ydah])
23
+ - Fix a false positive for `RSpec/FactoryBot/ConsistentParenthesesStyle` inside `&&`, `||` and `:?` when `omit_parentheses` is on ([@dmitrytsepelev])
24
+ - Fix a false positive for `RSpec/PendingWithoutReason` when pending/skip has a reason inside an example group. ([@ydah])
25
+ - Fix a false negative for `RSpec/RedundantAround` when redundant numblock `around`. ([@ydah])
26
+ - Change `RSpec/ContainExactly` to ignore calls with no arguments, and change `RSpec/MatchArray` to ignore calls with an empty array literal argument. ([@ydah], [@bquorning])
27
+ - Make `RSpec/MatchArray` and `RSpec/ContainExactly` pending. ([@ydah])
28
+
5
29
  ## 2.19.0 (2023-03-06)
6
30
 
7
31
  - Fix a false positive for `RSpec/ContextWording` when context is interpolated string literal or execute string. ([@ydah])
@@ -329,7 +353,7 @@
329
353
  ## 1.37.0 (2019-11-25)
330
354
 
331
355
  - Implement `RSpec/DescribedClassModuleWrapping` to disallow RSpec statements within a module. ([@kellysutton])
332
- - Fix documentation rake task to support Rubocop 0.75. ([@nickcampbell18])
356
+ - Fix documentation rake task to support RuboCop 0.75. ([@nickcampbell18])
333
357
  - Fix `RSpec/SubjectStub` to detect implicit subjects stubbed. ([@QQism])
334
358
  - Fix `RSpec/Pending` not flagging `skip` with string values. ([@pirj])
335
359
  - Add `AllowedExplicitMatchers` config option for `RSpec/PredicateMatcher`. ([@mkrawc])
@@ -660,7 +684,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
660
684
  - Skip `DescribeClass` cop for view specs. ([@andyw8])
661
685
  - Skip `FilePath` cop for Rails routing specs. ([@andyw8])
662
686
  - Add cop to check for focused specs. ([@renanborgescampos], [@jaredmoody])
663
- - Clean-up `RSpec::NotToNot` to use same configuration semantics as other Rubocop cops, add autocorrect support for `RSpec::NotToNot`. ([@baberthal])
687
+ - Clean-up `RSpec::NotToNot` to use same configuration semantics as other RuboCop cops, add autocorrect support for `RSpec::NotToNot`. ([@baberthal])
664
688
  - Update to rubocop 0.40.0. ([@nijikon])
665
689
 
666
690
  ## 1.4.1 (2016-04-03)
@@ -758,6 +782,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
758
782
  [@dduugg]: https://github.com/dduugg
759
783
  [@deivid-rodriguez]: https://github.com/deivid-rodriguez
760
784
  [@dgollahon]: https://github.com/dgollahon
785
+ [@dmitrytsepelev]: https://github.com/dmitrytsepelev
761
786
  [@drowze]: https://github.com/Drowze
762
787
  [@dswij]: https://github.com/dswij
763
788
  [@dvandersluis]: https://github.com/dvandersluis
@@ -800,6 +825,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
800
825
  [@mlarraz]: https://github.com/mlarraz
801
826
  [@mockdeep]: https://github.com/mockdeep
802
827
  [@mothonmars]: https://github.com/MothOnMars
828
+ [@mvz]: https://github.com/mvz
803
829
  [@nc-holodakg]: https://github.com/nc-holodakg
804
830
  [@nevir]: https://github.com/nevir
805
831
  [@ngouy]: https://github.com/ngouy
data/config/default.yml CHANGED
@@ -144,6 +144,12 @@ RSpec/Be:
144
144
  StyleGuide: https://rspec.rubystyle.guide/#be-matcher
145
145
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Be
146
146
 
147
+ RSpec/BeEmpty:
148
+ Description: Prefer using `be_empty` when checking for an empty array.
149
+ Enabled: pending
150
+ VersionAdded: '2.20'
151
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/BeEmpty
152
+
147
153
  RSpec/BeEq:
148
154
  Description: Check for expectations where `be(...)` can replace `eq(...)`.
149
155
  Enabled: pending
@@ -202,8 +208,8 @@ RSpec/ClassCheck:
202
208
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ClassCheck
203
209
 
204
210
  RSpec/ContainExactly:
205
- Description: Prefer `match_array` when matching array values.
206
- Enabled: true
211
+ Description: Checks where `contain_exactly` is used.
212
+ Enabled: pending
207
213
  VersionAdded: '2.19'
208
214
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ContainExactly
209
215
 
@@ -503,6 +509,13 @@ RSpec/ImplicitSubject:
503
509
  VersionChanged: '2.13'
504
510
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
505
511
 
512
+ RSpec/IndexedLet:
513
+ Description: Do not set up test data using indexes (e.g., `item_1`, `item_2`).
514
+ Enabled: pending
515
+ VersionAdded: '2.20'
516
+ Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/IndexedLet
517
+ Max: 1
518
+
506
519
  RSpec/InstanceSpy:
507
520
  Description: Checks for `instance_double` used with `have_received`.
508
521
  Enabled: true
@@ -563,8 +576,8 @@ RSpec/LetSetup:
563
576
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LetSetup
564
577
 
565
578
  RSpec/MatchArray:
566
- Description: Prefer `contain_exactly` when matching an array literal.
567
- Enabled: true
579
+ Description: Checks where `match_array` is used.
580
+ Enabled: pending
568
581
  VersionAdded: '2.19'
569
582
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MatchArray
570
583
 
@@ -1048,8 +1061,9 @@ RSpec/Rails/HttpStatus:
1048
1061
  SupportedStyles:
1049
1062
  - numeric
1050
1063
  - symbolic
1064
+ - be_status
1051
1065
  VersionAdded: '1.23'
1052
- VersionChanged: '2.0'
1066
+ VersionChanged: '2.20'
1053
1067
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HttpStatus
1054
1068
 
1055
1069
  RSpec/Rails/InferredSpecType:
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Prefer using `be_empty` when checking for an empty array.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(array).to contain_exactly
11
+ # expect(array).to match_array([])
12
+ #
13
+ # # good
14
+ # expect(array).to be_empty
15
+ #
16
+ class BeEmpty < Base
17
+ extend AutoCorrector
18
+
19
+ MSG = 'Use `be_empty` matchers for checking an empty array.'
20
+ RESTRICT_ON_SEND = %i[contain_exactly match_array].freeze
21
+
22
+ # @!method expect_array_matcher?(node)
23
+ def_node_matcher :expect_array_matcher?, <<~PATTERN
24
+ (send
25
+ (send nil? :expect _)
26
+ #Runners.all
27
+ ${
28
+ (send nil? :match_array (array))
29
+ (send nil? :contain_exactly)
30
+ }
31
+ )
32
+ PATTERN
33
+
34
+ def on_send(node)
35
+ expect_array_matcher?(node.parent) do |expect|
36
+ add_offense(expect) do |corrector|
37
+ corrector.replace(expect, 'be_empty')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -57,7 +57,7 @@ module RuboCop
57
57
  return unless be_nil_matcher?(node)
58
58
 
59
59
  add_offense(node, message: BE_MSG) do |corrector|
60
- corrector.replace(node.source_range, 'be(nil)')
60
+ corrector.replace(node, 'be(nil)')
61
61
  end
62
62
  end
63
63
 
@@ -65,7 +65,7 @@ module RuboCop
65
65
  return unless nil_value_expectation?(node)
66
66
 
67
67
  add_offense(node, message: BE_NIL_MSG) do |corrector|
68
- corrector.replace(node.source_range, 'be_nil')
68
+ corrector.replace(node, 'be_nil')
69
69
  end
70
70
  end
71
71
  end
@@ -59,15 +59,16 @@ module RuboCop
59
59
  #
60
60
  class ChangeByZero < Base
61
61
  extend AutoCorrector
62
- MSG = 'Prefer `not_to change` over `to change.by(0)`.'
62
+ MSG = 'Prefer `not_to change` over `to %<method>s.by(0)`.'
63
63
  MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
64
- 'over `change.by(0)`.'
65
- RESTRICT_ON_SEND = %i[change].freeze
64
+ 'over `%<method>s.by(0)`.'
65
+ CHANGE_METHODS = Set[:change, :a_block_changing, :changing].freeze
66
+ RESTRICT_ON_SEND = CHANGE_METHODS.freeze
66
67
 
67
68
  # @!method expect_change_with_arguments(node)
68
69
  def_node_matcher :expect_change_with_arguments, <<-PATTERN
69
70
  (send
70
- (send nil? :change ...) :by
71
+ $(send nil? CHANGE_METHODS ...) :by
71
72
  (int 0))
72
73
  PATTERN
73
74
 
@@ -75,48 +76,61 @@ module RuboCop
75
76
  def_node_matcher :expect_change_with_block, <<-PATTERN
76
77
  (send
77
78
  (block
78
- (send nil? :change)
79
+ $(send nil? CHANGE_METHODS)
79
80
  (args)
80
- (send (...) $_)) :by
81
+ (send (...) _)) :by
81
82
  (int 0))
82
83
  PATTERN
83
84
 
84
85
  # @!method change_nodes(node)
85
86
  def_node_search :change_nodes, <<-PATTERN
86
- $(send nil? :change ...)
87
+ $(send nil? CHANGE_METHODS ...)
87
88
  PATTERN
88
89
 
89
90
  def on_send(node)
90
- expect_change_with_arguments(node.parent) do
91
- check_offense(node.parent)
91
+ expect_change_with_arguments(node.parent) do |change|
92
+ register_offense(node.parent, change)
92
93
  end
93
94
 
94
- expect_change_with_block(node.parent.parent) do
95
- check_offense(node.parent.parent)
95
+ expect_change_with_block(node.parent.parent) do |change|
96
+ register_offense(node.parent.parent, change)
96
97
  end
97
98
  end
98
99
 
99
100
  private
100
101
 
101
- def check_offense(node)
102
- expression = node.source_range
102
+ # rubocop:disable Metrics/MethodLength
103
+ def register_offense(node, change_node)
103
104
  if compound_expectations?(node)
104
- add_offense(expression, message: message_compound) do |corrector|
105
+ add_offense(node.source_range,
106
+ message: message_compound(change_node)) do |corrector|
105
107
  autocorrect_compound(corrector, node)
106
108
  end
107
109
  else
108
- add_offense(expression) do |corrector|
109
- autocorrect(corrector, node)
110
+ add_offense(node.source_range,
111
+ message: message(change_node)) do |corrector|
112
+ autocorrect(corrector, node, change_node)
110
113
  end
111
114
  end
112
115
  end
116
+ # rubocop:enable Metrics/MethodLength
113
117
 
114
118
  def compound_expectations?(node)
115
119
  %i[and or & |].include?(node.parent.method_name)
116
120
  end
117
121
 
118
- def autocorrect(corrector, node)
122
+ def message(change_node)
123
+ format(MSG, method: change_node.method_name)
124
+ end
125
+
126
+ def message_compound(change_node)
127
+ format(MSG_COMPOUND, preferred: preferred_method,
128
+ method: change_node.method_name)
129
+ end
130
+
131
+ def autocorrect(corrector, node, change_node)
119
132
  corrector.replace(node.parent.loc.selector, 'not_to')
133
+ corrector.replace(change_node.loc.selector, 'change')
120
134
  range = node.loc.dot.with(end_pos: node.source_range.end_pos)
121
135
  corrector.remove(range)
122
136
  end
@@ -135,10 +149,6 @@ module RuboCop
135
149
  cop_config['NegatedMatcher']
136
150
  end
137
151
 
138
- def message_compound
139
- format(MSG_COMPOUND, preferred: preferred_method)
140
- end
141
-
142
152
  def preferred_method
143
153
  negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
144
154
  end
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Prefer `match_array` when matching array values.
6
+ # Checks where `contain_exactly` is used.
7
+ #
8
+ # This cop checks for the following:
9
+ # - Prefer `match_array` when matching array values.
10
+ # - Prefer `be_empty` when using `contain_exactly` with no arguments.
7
11
  #
8
12
  # @example
9
13
  # # bad
@@ -14,6 +18,7 @@ module RuboCop
14
18
  #
15
19
  # # good
16
20
  # it { is_expected.to contain_exactly(content, *array) }
21
+ #
17
22
  class ContainExactly < Base
18
23
  extend AutoCorrector
19
24
 
@@ -21,21 +26,27 @@ module RuboCop
21
26
  RESTRICT_ON_SEND = %i[contain_exactly].freeze
22
27
 
23
28
  def on_send(node)
29
+ return if node.arguments.empty?
30
+
31
+ check_populated_collection(node)
32
+ end
33
+
34
+ private
35
+
36
+ def check_populated_collection(node)
24
37
  return unless node.each_child_node.all?(&:splat_type?)
25
38
 
26
39
  add_offense(node) do |corrector|
27
- autocorrect(node, corrector)
40
+ autocorrect_for_populated_array(node, corrector)
28
41
  end
29
42
  end
30
43
 
31
- private
32
-
33
- def autocorrect(node, corrector)
44
+ def autocorrect_for_populated_array(node, corrector)
34
45
  arrays = node.arguments.map do |splat_node|
35
46
  splat_node.children.first
36
47
  end
37
48
  corrector.replace(
38
- node.source_range,
49
+ node,
39
50
  "match_array(#{arrays.map(&:source).join(' + ')})"
40
51
  )
41
52
  end
@@ -22,15 +22,15 @@ module RuboCop
22
22
  class DescribedClassModuleWrapping < Base
23
23
  MSG = 'Avoid opening modules and defining specs within them.'
24
24
 
25
- # @!method find_rspec_blocks(node)
26
- def_node_search :find_rspec_blocks, <<~PATTERN
27
- (block (send #explicit_rspec? #ExampleGroups.all ...) ...)
25
+ # @!method include_rspec_blocks?(node)
26
+ def_node_search :include_rspec_blocks?, <<~PATTERN
27
+ ({block numblock} (send #explicit_rspec? #ExampleGroups.all ...) ...)
28
28
  PATTERN
29
29
 
30
30
  def on_module(node)
31
- find_rspec_blocks(node) do
32
- add_offense(node)
33
- end
31
+ return unless include_rspec_blocks?(node)
32
+
33
+ add_offense(node)
34
34
  end
35
35
  end
36
36
  end
@@ -30,7 +30,7 @@ module RuboCop
30
30
  MSG = 'Add an empty line after `%<example_group>s`.'
31
31
 
32
32
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
33
- return unless example_group?(node)
33
+ return unless spec_group?(node)
34
34
 
35
35
  missing_separating_line_offense(node) do |method|
36
36
  format(MSG, example_group: method)
@@ -93,8 +93,8 @@ module RuboCop
93
93
  end
94
94
 
95
95
  def swap(corrector, actual, expected)
96
- corrector.replace(actual.source_range, expected.source)
97
- corrector.replace(expected.source_range, actual.source)
96
+ corrector.replace(actual, expected.source)
97
+ corrector.replace(expected, actual.source)
98
98
  end
99
99
  end
100
100
  end
@@ -100,10 +100,10 @@ module RuboCop
100
100
  end
101
101
  end
102
102
 
103
+ AMBIGUOUS_TYPES = %i[send pair array and or if].freeze
104
+
103
105
  def ambiguous_without_parentheses?(node)
104
- node.parent&.send_type? ||
105
- node.parent&.pair_type? ||
106
- node.parent&.array_type?
106
+ node.parent && AMBIGUOUS_TYPES.include?(node.parent.type)
107
107
  end
108
108
 
109
109
  def remove_parentheses(corrector, node)
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # Checks that spec file paths are consistent and well-formed.
7
7
  #
8
8
  # By default, this checks that spec file paths are consistent with the
9
- # test subject and and enforces that it reflects the described
9
+ # test subject and enforces that it reflects the described
10
10
  # class/module and its optionally called out method.
11
11
  #
12
12
  # With the configuration option `IgnoreMethods` the called out method will
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Do not set up test data using indexes (e.g., `item_1`, `item_2`).
7
+ #
8
+ # It makes reading the test harder because it's not clear what exactly
9
+ # is tested by this particular example.
10
+ #
11
+ # @example `Max: 1 (default)`
12
+ # # bad
13
+ # let(:item_1) { create(:item) }
14
+ # let(:item_2) { create(:item) }
15
+ #
16
+ # let(:item1) { create(:item) }
17
+ # let(:item2) { create(:item) }
18
+ #
19
+ # # good
20
+ #
21
+ # let(:visible_item) { create(:item, visible: true) }
22
+ # let(:invisible_item) { create(:item, visible: false) }
23
+ #
24
+ # @example `Max: 2`
25
+ # # bad
26
+ # let(:item_1) { create(:item) }
27
+ # let(:item_2) { create(:item) }
28
+ # let(:item_3) { create(:item) }
29
+ #
30
+ # # good
31
+ # let(:item_1) { create(:item) }
32
+ # let(:item_2) { create(:item) }
33
+ #
34
+ class IndexedLet < Base
35
+ MSG = 'This `let` statement uses index in its name. Please give it ' \
36
+ 'a meaningful name.'
37
+
38
+ # @!method let_name(node)
39
+ def_node_matcher :let_name, <<~PATTERN
40
+ {
41
+ (block (send nil? #Helpers.all ({str sym} $_) ...) ...)
42
+ (send nil? #Helpers.all ({str sym} $_) block_pass)
43
+ }
44
+ PATTERN
45
+
46
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
47
+ return unless spec_group?(node)
48
+
49
+ children = node.body&.child_nodes
50
+ return unless children
51
+
52
+ filter_indexed_lets(children).each do |let_node|
53
+ add_offense(let_node)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ SUFFIX_INDEX_REGEX = /_?\d+$/.freeze
60
+ INDEX_REGEX = /\d+/.freeze
61
+
62
+ def filter_indexed_lets(candidates)
63
+ candidates
64
+ .filter { |node| indexed_let?(node) }
65
+ .group_by { |node| let_name_stripped_index(node) }
66
+ .values
67
+ .filter { |lets| lets.length > cop_config['Max'] }
68
+ .flatten
69
+ end
70
+
71
+ def indexed_let?(node)
72
+ let?(node) && SUFFIX_INDEX_REGEX.match?(let_name(node))
73
+ end
74
+
75
+ def let_name_stripped_index(node)
76
+ let_name(node).to_s.gsub(INDEX_REGEX, '')
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -3,7 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Prefer `contain_exactly` when matching an array literal.
6
+ # Checks where `match_array` is used.
7
+ #
8
+ # This cop checks for the following:
9
+ # - Prefer `contain_exactly` when matching an array with values.
10
+ # - Prefer `eq` when using `match_array` with an empty array literal.
7
11
  #
8
12
  # @example
9
13
  # # bad
@@ -17,20 +21,34 @@ module RuboCop
17
21
  #
18
22
  # # good
19
23
  # it { is_expected.to match_array(%w(tremble in fear foolish mortals)) }
24
+ #
20
25
  class MatchArray < Base
21
26
  extend AutoCorrector
22
27
 
23
28
  MSG = 'Prefer `contain_exactly` when matching an array literal.'
24
29
  RESTRICT_ON_SEND = %i[match_array].freeze
25
30
 
31
+ # @!method match_array_with_empty_array?(node)
32
+ def_node_matcher :match_array_with_empty_array?, <<~PATTERN
33
+ (send nil? :match_array (array))
34
+ PATTERN
35
+
26
36
  def on_send(node)
27
- return unless node.first_argument.array_type?
37
+ return unless node.first_argument&.array_type?
38
+ return if match_array_with_empty_array?(node)
39
+
40
+ check_populated_array(node)
41
+ end
42
+
43
+ private
44
+
45
+ def check_populated_array(node)
28
46
  return if node.first_argument.percent_literal?
29
47
 
30
48
  add_offense(node) do |corrector|
31
49
  array_contents = node.arguments.flat_map(&:to_a)
32
50
  corrector.replace(
33
- node.source_range,
51
+ node,
34
52
  "contain_exactly(#{array_contents.map(&:source).join(', ')})"
35
53
  )
36
54
  end
@@ -70,7 +70,12 @@ module RuboCop
70
70
 
71
71
  # @!method skipped_by_example_method?(node)
72
72
  def_node_matcher :skipped_by_example_method?, <<~PATTERN
73
- (send nil? ${#Examples.skipped #Examples.pending} ...)
73
+ (send nil? ${#Examples.skipped #Examples.pending})
74
+ PATTERN
75
+
76
+ # @!method skipped_by_example_method_with_block?(node)
77
+ def_node_matcher :skipped_by_example_method_with_block?, <<~PATTERN
78
+ ({block numblock} (send nil? ${#Examples.skipped #Examples.pending} ...) ...)
74
79
  PATTERN
75
80
 
76
81
  # @!method metadata_without_reason?(node)
@@ -102,7 +107,7 @@ module RuboCop
102
107
  on_skipped_by_example_method(node)
103
108
  on_skipped_by_example_group_method(node)
104
109
  elsif example?(parent)
105
- on_skipped_by_in_example_method(node, parent)
110
+ on_skipped_by_in_example_method(node)
106
111
  end
107
112
  end
108
113
 
@@ -121,7 +126,7 @@ module RuboCop
121
126
  explicit_rspec?(node.receiver)
122
127
  end
123
128
 
124
- def on_skipped_by_in_example_method(node, _direct_parent)
129
+ def on_skipped_by_in_example_method(node)
125
130
  skipped_in_example?(node) do |pending|
126
131
  add_offense(node, message: "Give the reason for #{pending}.")
127
132
  end
@@ -137,6 +142,10 @@ module RuboCop
137
142
  skipped_by_example_method?(node) do |pending|
138
143
  add_offense(node, message: "Give the reason for #{pending}.")
139
144
  end
145
+
146
+ skipped_by_example_method_with_block?(node.parent) do |pending|
147
+ add_offense(node, message: "Give the reason for #{pending}.")
148
+ end
140
149
  end
141
150
 
142
151
  def on_skipped_by_example_group_method(node)
@@ -99,7 +99,7 @@ module RuboCop
99
99
  block = block_loc ? block_loc.source : ''
100
100
 
101
101
  corrector.replace(
102
- matcher.source_range,
102
+ matcher,
103
103
  to_predicate_matcher(predicate.method_name) + args + block
104
104
  )
105
105
  end
@@ -214,7 +214,7 @@ module RuboCop
214
214
 
215
215
  def corrector_explicit(corrector, to_node, actual, matcher, block_child)
216
216
  replacement_matcher = replacement_matcher(to_node)
217
- corrector.replace(matcher.source_range, replacement_matcher)
217
+ corrector.replace(matcher, replacement_matcher)
218
218
  move_predicate(corrector, actual, matcher, block_child)
219
219
  corrector.replace(to_node.loc.selector, 'to')
220
220
  end
@@ -226,8 +226,7 @@ module RuboCop
226
226
  block = block_loc ? block_loc.source : ''
227
227
 
228
228
  corrector.remove(block_loc) if block_loc
229
- corrector.insert_after(actual.source_range,
230
- ".#{predicate}" + args + block)
229
+ corrector.insert_after(actual, ".#{predicate}" + args + block)
231
230
  end
232
231
 
233
232
  # rubocop:disable Metrics/MethodLength
@@ -9,6 +9,7 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # expect(response.status).to be(200)
12
+ # expect(response.code).to eq("200")
12
13
  #
13
14
  # # good
14
15
  # expect(response).to have_http_status(200)
@@ -17,8 +18,8 @@ module RuboCop
17
18
  extend AutoCorrector
18
19
 
19
20
  MSG =
20
- 'Prefer `expect(response).%<to>s have_http_status(%<status>i)` ' \
21
- 'over `expect(response.status).%<to>s %<match>s`.'
21
+ 'Prefer `expect(response).%<to>s have_http_status(%<status>s)` ' \
22
+ 'over `%<bad_code>s`.'
22
23
 
23
24
  RUNNERS = %i[to to_not not_to].to_set
24
25
  RESTRICT_ON_SEND = RUNNERS
@@ -27,19 +28,23 @@ module RuboCop
27
28
  def_node_matcher :match_status, <<-PATTERN
28
29
  (send
29
30
  (send nil? :expect
30
- $(send (send nil? :response) :status)
31
+ $(send (send nil? :response) {:status :code})
31
32
  )
32
33
  $RUNNERS
33
- $(send nil? {:be :eq :eql :equal} (int $_))
34
+ $(send nil? {:be :eq :eql :equal} ({int str} $_))
34
35
  )
35
36
  PATTERN
36
37
 
37
38
  def on_send(node)
38
39
  match_status(node) do |response_status, to, match, status|
39
- message = format(MSG, to: to, match: match.source, status: status)
40
+ return unless status.to_s.match?(/\A\d+\z/)
41
+
42
+ message = format(MSG, to: to, status: status,
43
+ bad_code: node.source)
40
44
  add_offense(node, message: message) do |corrector|
41
- corrector.replace(response_status.source_range, 'response')
45
+ corrector.replace(response_status, 'response')
42
46
  corrector.replace(match.loc.selector, 'have_http_status')
47
+ corrector.replace(match.first_argument, status.to_s)
43
48
  end
44
49
  end
45
50
  end
@@ -8,6 +8,11 @@ module RuboCop
8
8
  module Rails
9
9
  # Enforces use of symbolic or numeric value to describe HTTP status.
10
10
  #
11
+ # This cop inspects only `have_http_status` calls.
12
+ # So, this cop does not check if a method starting with `be_*` is used
13
+ # when setting for `EnforcedStyle: symbolic` or
14
+ # `EnforcedStyle: numeric`.
15
+ #
11
16
  # @example `EnforcedStyle: symbolic` (default)
12
17
  # # bad
13
18
  # it { is_expected.to have_http_status 200 }
@@ -30,6 +35,19 @@ module RuboCop
30
35
  # it { is_expected.to have_http_status :success }
31
36
  # it { is_expected.to have_http_status :error }
32
37
  #
38
+ # @example `EnforcedStyle: be_status`
39
+ # # bad
40
+ # it { is_expected.to have_http_status :ok }
41
+ # it { is_expected.to have_http_status :not_found }
42
+ # it { is_expected.to have_http_status 200 }
43
+ # it { is_expected.to have_http_status 404 }
44
+ #
45
+ # # good
46
+ # it { is_expected.to be_ok }
47
+ # it { is_expected.to be_not_found }
48
+ # it { is_expected.to have_http_status :success }
49
+ # it { is_expected.to have_http_status :error }
50
+ #
33
51
  class HttpStatus < Base
34
52
  extend AutoCorrector
35
53
  include ConfigurableEnforcedStyle
@@ -41,12 +59,13 @@ module RuboCop
41
59
  PATTERN
42
60
 
43
61
  def on_send(node)
44
- http_status(node) do |ast_node|
45
- checker = checker_class.new(ast_node)
62
+ http_status(node) do |arg|
63
+ checker = checker_class.new(arg)
46
64
  return unless checker.offensive?
47
65
 
48
- add_offense(checker.node, message: checker.message) do |corrector|
49
- corrector.replace(checker.node, checker.preferred_style)
66
+ add_offense(checker.offense_range,
67
+ message: checker.message) do |corrector|
68
+ corrector.replace(checker.offense_range, checker.prefer)
50
69
  end
51
70
  end
52
71
  end
@@ -59,13 +78,16 @@ module RuboCop
59
78
  SymbolicStyleChecker
60
79
  when :numeric
61
80
  NumericStyleChecker
81
+ when :be_status
82
+ BeStatusStyleChecker
62
83
  end
63
84
  end
64
85
 
65
86
  # :nodoc:
66
- class SymbolicStyleChecker
87
+ class StyleCheckerBase
67
88
  MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
68
89
  'to describe HTTP status code.'
90
+ ALLOWED_STATUSES = %i[error success missing redirect].freeze
69
91
 
70
92
  attr_reader :node
71
93
 
@@ -73,16 +95,36 @@ module RuboCop
73
95
  @node = node
74
96
  end
75
97
 
98
+ def message
99
+ format(MSG, prefer: prefer, current: current)
100
+ end
101
+
102
+ def offense_range
103
+ node
104
+ end
105
+
106
+ def allowed_symbol?
107
+ node.sym_type? && ALLOWED_STATUSES.include?(node.value)
108
+ end
109
+
110
+ def custom_http_status_code?
111
+ node.int_type? &&
112
+ !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i)
113
+ end
114
+ end
115
+
116
+ # :nodoc:
117
+ class SymbolicStyleChecker < StyleCheckerBase
76
118
  def offensive?
77
119
  !node.sym_type? && !custom_http_status_code?
78
120
  end
79
121
 
80
- def message
81
- format(MSG, prefer: preferred_style, current: number.to_s)
122
+ def prefer
123
+ symbol.inspect
82
124
  end
83
125
 
84
- def preferred_style
85
- symbol.inspect
126
+ def current
127
+ number.inspect
86
128
  end
87
129
 
88
130
  private
@@ -94,50 +136,64 @@ module RuboCop
94
136
  def number
95
137
  node.source.to_i
96
138
  end
97
-
98
- def custom_http_status_code?
99
- node.int_type? &&
100
- !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i)
101
- end
102
139
  end
103
140
 
104
141
  # :nodoc:
105
- class NumericStyleChecker
106
- MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
107
- 'to describe HTTP status code.'
108
-
109
- ALLOWED_STATUSES = %i[error success missing redirect].freeze
110
-
111
- attr_reader :node
112
-
113
- def initialize(node)
114
- @node = node
115
- end
116
-
142
+ class NumericStyleChecker < StyleCheckerBase
117
143
  def offensive?
118
144
  !node.int_type? && !allowed_symbol?
119
145
  end
120
146
 
121
- def message
122
- format(MSG, prefer: preferred_style, current: symbol.inspect)
147
+ def prefer
148
+ number.to_s
123
149
  end
124
150
 
125
- def preferred_style
126
- number.to_s
151
+ def current
152
+ symbol.inspect
127
153
  end
128
154
 
129
155
  private
130
156
 
157
+ def symbol
158
+ node.value
159
+ end
160
+
131
161
  def number
132
162
  ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
133
163
  end
164
+ end
165
+
166
+ # :nodoc:
167
+ class BeStatusStyleChecker < StyleCheckerBase
168
+ def offensive?
169
+ (!node.sym_type? && !custom_http_status_code?) ||
170
+ (!node.int_type? && !allowed_symbol?)
171
+ end
172
+
173
+ def offense_range
174
+ node.parent
175
+ end
176
+
177
+ def prefer
178
+ if node.sym_type?
179
+ "be_#{node.value}"
180
+ else
181
+ "be_#{symbol}"
182
+ end
183
+ end
184
+
185
+ def current
186
+ offense_range.source
187
+ end
188
+
189
+ private
134
190
 
135
191
  def symbol
136
- node.value
192
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
137
193
  end
138
194
 
139
- def allowed_symbol?
140
- node.sym_type? && ALLOWED_STATUSES.include?(node.value)
195
+ def number
196
+ node.source.to_i
141
197
  end
142
198
  end
143
199
  end
@@ -13,9 +13,9 @@ module RuboCop
13
13
  # refute_equal(a, b)
14
14
  #
15
15
  # # good
16
- # expect(a).to eq(b)
17
- # expect(a).to(eq(b), "must be equal")
18
- # expect(a).not_to eq(b)
16
+ # expect(b).to eq(a)
17
+ # expect(b).to(eq(a), "must be equal")
18
+ # expect(b).not_to eq(a)
19
19
  #
20
20
  class MinitestAssertions < Base
21
21
  extend AutoCorrector
@@ -43,9 +43,9 @@ module RuboCop
43
43
  def replacement(node, expected, actual, failure_message)
44
44
  runner = node.method?(:assert_equal) ? 'to' : 'not_to'
45
45
  if failure_message.nil?
46
- "expect(#{expected.source}).#{runner} eq(#{actual.source})"
46
+ "expect(#{actual.source}).#{runner} eq(#{expected.source})"
47
47
  else
48
- "expect(#{expected.source}).#{runner}(eq(#{actual.source}), " \
48
+ "expect(#{actual.source}).#{runner}(eq(#{expected.source}), " \
49
49
  "#{failure_message.source})"
50
50
  end
51
51
  end
@@ -41,11 +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
45
- (send _ :around ...)
46
- (args _?)
47
- (send _ :run)
48
- )
44
+ ({block numblock} (send _ :around ...) ... (send _ :run))
49
45
  PATTERN
50
46
 
51
47
  # @!method match_redundant_around_hook_send?(node)
@@ -23,6 +23,9 @@ module RuboCop
23
23
  # end
24
24
  #
25
25
  class ScatteredSetup < Base
26
+ include RangeHelp
27
+ extend AutoCorrector
28
+
26
29
  MSG = 'Do not define multiple `%<hook_name>s` hooks in the same ' \
27
30
  'example group (also defined on %<lines>s).'
28
31
 
@@ -30,13 +33,11 @@ module RuboCop
30
33
  return unless example_group?(node)
31
34
 
32
35
  repeated_hooks(node).each do |occurrences|
33
- lines = occurrences.map(&:first_line)
34
-
35
36
  occurrences.each do |occurrence|
36
- lines_except_current = lines - [occurrence.first_line]
37
- message = format(MSG, hook_name: occurrences.first.method_name,
38
- lines: lines_msg(lines_except_current))
39
- add_offense(occurrence, message: message)
37
+ message = message(occurrences, occurrence)
38
+ add_offense(occurrence, message: message) do |corrector|
39
+ autocorrect(corrector, occurrences.first, occurrence)
40
+ end
40
41
  end
41
42
  end
42
43
  end
@@ -63,6 +64,22 @@ module RuboCop
63
64
  "lines #{numbers.join(', ')}"
64
65
  end
65
66
  end
67
+
68
+ def message(occurrences, occurrence)
69
+ lines = occurrences.map(&:first_line)
70
+ lines_except_current = lines - [occurrence.first_line]
71
+ format(MSG, hook_name: occurrences.first.method_name,
72
+ lines: lines_msg(lines_except_current))
73
+ end
74
+
75
+ def autocorrect(corrector, first_occurrence, occurrence)
76
+ return if first_occurrence == occurrence || !first_occurrence.body
77
+
78
+ corrector.insert_after(first_occurrence.body,
79
+ "\n#{occurrence.body.source}")
80
+ corrector.remove(range_by_whole_lines(occurrence.source_range,
81
+ include_final_newline: true))
82
+ end
66
83
  end
67
84
  end
68
85
  end
@@ -32,6 +32,7 @@ require_relative 'rspec/align_right_let_brace'
32
32
  require_relative 'rspec/any_instance'
33
33
  require_relative 'rspec/around_block'
34
34
  require_relative 'rspec/be'
35
+ require_relative 'rspec/be_empty'
35
36
  require_relative 'rspec/be_eq'
36
37
  require_relative 'rspec/be_eql'
37
38
  require_relative 'rspec/be_nil'
@@ -71,6 +72,7 @@ require_relative 'rspec/identical_equality_assertion'
71
72
  require_relative 'rspec/implicit_block_expectation'
72
73
  require_relative 'rspec/implicit_expect'
73
74
  require_relative 'rspec/implicit_subject'
75
+ require_relative 'rspec/indexed_let'
74
76
  require_relative 'rspec/instance_spy'
75
77
  require_relative 'rspec/instance_variable'
76
78
  require_relative 'rspec/it_behaves_like'
@@ -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.19.0'
7
+ STRING = '2.21.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.19.0
4
+ version: 2.21.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-03-06 00:00:00.000000000 Z
13
+ date: 2023-05-05 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -66,6 +66,7 @@ files:
66
66
  - lib/rubocop/cop/rspec/around_block.rb
67
67
  - lib/rubocop/cop/rspec/base.rb
68
68
  - lib/rubocop/cop/rspec/be.rb
69
+ - lib/rubocop/cop/rspec/be_empty.rb
69
70
  - lib/rubocop/cop/rspec/be_eq.rb
70
71
  - lib/rubocop/cop/rspec/be_eql.rb
71
72
  - lib/rubocop/cop/rspec/be_nil.rb
@@ -119,6 +120,7 @@ files:
119
120
  - lib/rubocop/cop/rspec/implicit_block_expectation.rb
120
121
  - lib/rubocop/cop/rspec/implicit_expect.rb
121
122
  - lib/rubocop/cop/rspec/implicit_subject.rb
123
+ - lib/rubocop/cop/rspec/indexed_let.rb
122
124
  - lib/rubocop/cop/rspec/instance_spy.rb
123
125
  - lib/rubocop/cop/rspec/instance_variable.rb
124
126
  - lib/rubocop/cop/rspec/it_behaves_like.rb
@@ -220,14 +222,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
220
222
  requirements:
221
223
  - - ">="
222
224
  - !ruby/object:Gem::Version
223
- version: 2.6.0
225
+ version: 2.7.0
224
226
  required_rubygems_version: !ruby/object:Gem::Requirement
225
227
  requirements:
226
228
  - - ">="
227
229
  - !ruby/object:Gem::Version
228
230
  version: '0'
229
231
  requirements: []
230
- rubygems_version: 3.3.26
232
+ rubygems_version: 3.4.12
231
233
  signing_key:
232
234
  specification_version: 4
233
235
  summary: Code style checking for RSpec files