rubocop-rspec 2.19.0 → 2.21.0

Sign up to get free protection for your applications and to get access to all the features.
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