rubocop-rspec 2.16.0 → 2.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +124 -9
  3. data/README.md +3 -3
  4. data/config/default.yml +145 -18
  5. data/config/obsoletion.yml +15 -0
  6. data/lib/rubocop/cop/rspec/be_empty.rb +44 -0
  7. data/lib/rubocop/cop/rspec/be_nil.rb +2 -2
  8. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +29 -115
  9. data/lib/rubocop/cop/rspec/capybara/match_style.rb +38 -0
  10. data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +23 -96
  11. data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +19 -75
  12. data/lib/rubocop/cop/rspec/capybara/specific_finders.rb +14 -83
  13. data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +25 -69
  14. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +26 -63
  15. data/lib/rubocop/cop/rspec/change_by_zero.rb +33 -23
  16. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  17. data/lib/rubocop/cop/rspec/context_method.rb +5 -1
  18. data/lib/rubocop/cop/rspec/context_wording.rb +13 -6
  19. data/lib/rubocop/cop/rspec/describe_method.rb +16 -8
  20. data/lib/rubocop/cop/rspec/described_class.rb +2 -1
  21. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +7 -5
  22. data/lib/rubocop/cop/rspec/dialect.rb +1 -1
  23. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +2 -2
  24. data/lib/rubocop/cop/rspec/empty_example_group.rb +10 -7
  25. data/lib/rubocop/cop/rspec/empty_hook.rb +2 -2
  26. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +1 -1
  27. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  28. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  29. data/lib/rubocop/cop/rspec/example_wording.rb +1 -1
  30. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +14 -5
  31. data/lib/rubocop/cop/rspec/expect_actual.rb +4 -4
  32. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -1
  33. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +25 -118
  34. data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +40 -107
  35. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +30 -250
  36. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +19 -46
  37. data/lib/rubocop/cop/rspec/factory_bot/factory_name_style.rb +23 -64
  38. data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +45 -79
  39. data/lib/rubocop/cop/rspec/file_path.rb +8 -2
  40. data/lib/rubocop/cop/rspec/focus.rb +19 -5
  41. data/lib/rubocop/cop/rspec/hook_argument.rb +12 -9
  42. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +5 -3
  43. data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
  44. data/lib/rubocop/cop/rspec/instance_variable.rb +1 -1
  45. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -1
  46. data/lib/rubocop/cop/rspec/let_before_examples.rb +8 -4
  47. data/lib/rubocop/cop/rspec/let_setup.rb +6 -8
  48. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  49. data/lib/rubocop/cop/rspec/metadata_style.rb +197 -0
  50. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +1 -2
  51. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  52. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  53. data/lib/rubocop/cop/rspec/mixin/metadata.rb +21 -7
  54. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +20 -4
  55. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -1
  56. data/lib/rubocop/cop/rspec/named_subject.rb +7 -5
  57. data/lib/rubocop/cop/rspec/no_expectation_example.rb +2 -5
  58. data/lib/rubocop/cop/rspec/overwriting_setup.rb +3 -1
  59. data/lib/rubocop/cop/rspec/pending.rb +23 -13
  60. data/lib/rubocop/cop/rspec/pending_without_reason.rb +72 -36
  61. data/lib/rubocop/cop/rspec/predicate_matcher.rb +49 -40
  62. data/lib/rubocop/cop/rspec/rails/have_http_status.rb +11 -6
  63. data/lib/rubocop/cop/rspec/rails/http_status.rb +107 -34
  64. data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +4 -4
  65. data/lib/rubocop/cop/rspec/rails/minitest_assertions.rb +60 -0
  66. data/lib/rubocop/cop/rspec/rails/negation_be_valid.rb +102 -0
  67. data/lib/rubocop/cop/rspec/rails/travel_around.rb +92 -0
  68. data/lib/rubocop/cop/rspec/receive_counts.rb +1 -1
  69. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  70. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  71. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +3 -6
  72. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +3 -6
  73. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -4
  74. data/lib/rubocop/cop/rspec/scattered_setup.rb +23 -6
  75. data/lib/rubocop/cop/rspec/shared_context.rb +12 -13
  76. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -4
  77. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  78. data/lib/rubocop/cop/rspec/sort_metadata.rb +4 -3
  79. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  80. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  81. data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -1
  82. data/lib/rubocop/cop/rspec/subject_stub.rb +0 -1
  83. data/lib/rubocop/cop/rspec/variable_definition.rb +5 -2
  84. data/lib/rubocop/cop/rspec/variable_name.rb +4 -1
  85. data/lib/rubocop/cop/rspec/verified_double_reference.rb +7 -7
  86. data/lib/rubocop/cop/rspec/verified_doubles.rb +1 -1
  87. data/lib/rubocop/cop/rspec/void_expect.rb +2 -1
  88. data/lib/rubocop/cop/rspec_cops.rb +16 -0
  89. data/lib/rubocop/rspec/config_formatter.rb +16 -0
  90. data/lib/rubocop/rspec/example_group.rb +6 -8
  91. data/lib/rubocop/rspec/language/node_pattern.rb +26 -0
  92. data/lib/rubocop/rspec/language.rb +25 -16
  93. data/lib/rubocop/rspec/version.rb +1 -1
  94. data/lib/rubocop-rspec.rb +4 -5
  95. metadata +50 -8
  96. data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +0 -80
  97. data/lib/rubocop/cop/rspec/mixin/css_selector.rb +0 -146
  98. data/lib/rubocop/rspec/factory_bot/language.rb +0 -37
  99. data/lib/rubocop/rspec/factory_bot.rb +0 -64
@@ -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]}"
@@ -84,22 +84,22 @@ module RuboCop
84
84
 
85
85
  def remove_predicate(corrector, predicate)
86
86
  range = predicate.loc.dot.with(
87
- end_pos: predicate.loc.expression.end_pos
87
+ end_pos: predicate.source_range.end_pos
88
88
  )
89
89
 
90
90
  corrector.remove(range)
91
91
 
92
- block_range = block_loc(predicate)
92
+ block_range = LocationHelp.block_with_whitespace(predicate)
93
93
  corrector.remove(block_range) if block_range
94
94
  end
95
95
 
96
96
  def rewrite_matcher(corrector, predicate, matcher)
97
- args = args_loc(predicate).source
98
- block_loc = block_loc(predicate)
97
+ args = LocationHelp.arguments_with_whitespace(predicate).source
98
+ block_loc = LocationHelp.block_with_whitespace(predicate)
99
99
  block = block_loc ? block_loc.source : ''
100
100
 
101
101
  corrector.replace(
102
- matcher.loc.expression,
102
+ matcher,
103
103
  to_predicate_matcher(predicate.method_name) + args + block
104
104
  )
105
105
  end
@@ -118,7 +118,7 @@ module RuboCop
118
118
  end
119
119
 
120
120
  # A helper for `explicit` style
121
- module ExplicitHelper
121
+ module ExplicitHelper # rubocop:disable Metrics/ModuleLength
122
122
  include RuboCop::RSpec::Language
123
123
  extend NodePattern::Macros
124
124
 
@@ -149,12 +149,35 @@ module RuboCop
149
149
  return if part_of_ignored_node?(node)
150
150
 
151
151
  predicate_matcher?(node) do |actual, matcher|
152
+ next unless replaceable_matcher?(matcher)
153
+
152
154
  add_offense(node, message: message_explicit(matcher)) do |corrector|
155
+ next if uncorrectable_matcher?(node, matcher)
156
+
153
157
  corrector_explicit(corrector, node, actual, matcher, matcher)
154
158
  end
155
159
  end
156
160
  end
157
161
 
162
+ def replaceable_matcher?(matcher)
163
+ case matcher.method_name.to_s
164
+ when 'include'
165
+ matcher.arguments.one?
166
+ else
167
+ true
168
+ end
169
+ end
170
+
171
+ def uncorrectable_matcher?(node, matcher)
172
+ heredoc_argument?(matcher) && !same_line?(node, matcher)
173
+ end
174
+
175
+ def heredoc_argument?(matcher)
176
+ matcher.arguments.select do |arg|
177
+ %i[str dstr xstr].include?(arg.type)
178
+ end.any?(&:heredoc?)
179
+ end
180
+
158
181
  # @!method predicate_matcher?(node)
159
182
  def_node_matcher :predicate_matcher?, <<-PATTERN
160
183
  (send
@@ -179,7 +202,8 @@ module RuboCop
179
202
 
180
203
  return false if allowed_explicit_matchers.include?(name)
181
204
 
182
- name.start_with?('be_', 'have_') && !name.end_with?('?')
205
+ name.start_with?('be_', 'have_') && !name.end_with?('?') ||
206
+ %w[include respond_to].include?(name)
183
207
  end
184
208
 
185
209
  def message_explicit(matcher)
@@ -190,20 +214,19 @@ module RuboCop
190
214
 
191
215
  def corrector_explicit(corrector, to_node, actual, matcher, block_child)
192
216
  replacement_matcher = replacement_matcher(to_node)
193
- corrector.replace(matcher.loc.expression, replacement_matcher)
217
+ corrector.replace(matcher, replacement_matcher)
194
218
  move_predicate(corrector, actual, matcher, block_child)
195
219
  corrector.replace(to_node.loc.selector, 'to')
196
220
  end
197
221
 
198
222
  def move_predicate(corrector, actual, matcher, block_child)
199
223
  predicate = to_predicate_method(matcher.method_name)
200
- args = args_loc(matcher).source
201
- block_loc = block_loc(block_child)
224
+ args = LocationHelp.arguments_with_whitespace(matcher).source
225
+ block_loc = LocationHelp.block_with_whitespace(block_child)
202
226
  block = block_loc ? block_loc.source : ''
203
227
 
204
228
  corrector.remove(block_loc) if block_loc
205
- corrector.insert_after(actual.loc.expression,
206
- ".#{predicate}" + args + block)
229
+ corrector.insert_after(actual, ".#{predicate}" + args + block)
207
230
  end
208
231
 
209
232
  # rubocop:disable Metrics/MethodLength
@@ -217,10 +240,10 @@ module RuboCop
217
240
  'include?'
218
241
  when 'respond_to'
219
242
  'respond_to?'
220
- when /^have_(.+)/
243
+ when /\Ahave_(.+)/
221
244
  "has_#{Regexp.last_match(1)}?"
222
245
  else
223
- "#{matcher[/^be_(.+)/, 1]}?"
246
+ "#{matcher[/\Abe_(.+)/, 1]}?"
224
247
  end
225
248
  end
226
249
  # rubocop:enable Metrics/MethodLength
@@ -270,6 +293,17 @@ module RuboCop
270
293
  # # good - the above code is rewritten to it by this cop
271
294
  # expect(foo.something?).to be(true)
272
295
  #
296
+ # # bad - no autocorrect
297
+ # expect(foo)
298
+ # .to be_something(<<~TEXT)
299
+ # bar
300
+ # TEXT
301
+ #
302
+ # # good
303
+ # expect(foo.something?(<<~TEXT)).to be(true)
304
+ # bar
305
+ # TEXT
306
+ #
273
307
  # @example Strict: false, EnforcedStyle: explicit
274
308
  # # bad
275
309
  # expect(foo).to be_something
@@ -297,31 +331,6 @@ module RuboCop
297
331
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
298
332
  check_explicit(node) if style == :explicit
299
333
  end
300
-
301
- private
302
-
303
- # returns args location with whitespace
304
- # @example
305
- # foo 1, 2
306
- # ^^^^^
307
- def args_loc(send_node)
308
- send_node.loc.selector.end.with(
309
- end_pos: send_node.loc.expression.end_pos
310
- )
311
- end
312
-
313
- # returns block location with whitespace
314
- # @example
315
- # foo { bar }
316
- # ^^^^^^^^
317
- def block_loc(send_node)
318
- parent = send_node.parent
319
- return unless parent.block_type?
320
-
321
- send_node.loc.expression.end.with(
322
- end_pos: parent.loc.expression.end_pos
323
- )
324
- end
325
334
  end
326
335
  end
327
336
  end
@@ -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,14 +8,21 @@ 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 }
14
19
  # it { is_expected.to have_http_status 404 }
20
+ # it { is_expected.to have_http_status "403" }
15
21
  #
16
22
  # # good
17
23
  # it { is_expected.to have_http_status :ok }
18
24
  # it { is_expected.to have_http_status :not_found }
25
+ # it { is_expected.to have_http_status :forbidden }
19
26
  # it { is_expected.to have_http_status :success }
20
27
  # it { is_expected.to have_http_status :error }
21
28
  #
@@ -23,10 +30,27 @@ module RuboCop
23
30
  # # bad
24
31
  # it { is_expected.to have_http_status :ok }
25
32
  # it { is_expected.to have_http_status :not_found }
33
+ # it { is_expected.to have_http_status "forbidden" }
26
34
  #
27
35
  # # good
28
36
  # it { is_expected.to have_http_status 200 }
29
37
  # it { is_expected.to have_http_status 404 }
38
+ # it { is_expected.to have_http_status 403 }
39
+ # it { is_expected.to have_http_status :success }
40
+ # it { is_expected.to have_http_status :error }
41
+ #
42
+ # @example `EnforcedStyle: be_status`
43
+ # # bad
44
+ # it { is_expected.to have_http_status :ok }
45
+ # it { is_expected.to have_http_status :not_found }
46
+ # it { is_expected.to have_http_status "forbidden" }
47
+ # it { is_expected.to have_http_status 200 }
48
+ # it { is_expected.to have_http_status 404 }
49
+ # it { is_expected.to have_http_status "403" }
50
+ #
51
+ # # good
52
+ # it { is_expected.to be_ok }
53
+ # it { is_expected.to be_not_found }
30
54
  # it { is_expected.to have_http_status :success }
31
55
  # it { is_expected.to have_http_status :error }
32
56
  #
@@ -37,16 +61,17 @@ module RuboCop
37
61
 
38
62
  # @!method http_status(node)
39
63
  def_node_matcher :http_status, <<-PATTERN
40
- (send nil? :have_http_status ${int sym})
64
+ (send nil? :have_http_status ${int sym str})
41
65
  PATTERN
42
66
 
43
67
  def on_send(node)
44
- http_status(node) do |ast_node|
45
- checker = checker_class.new(ast_node)
68
+ http_status(node) do |arg|
69
+ checker = checker_class.new(arg)
46
70
  return unless checker.offensive?
47
71
 
48
- add_offense(checker.node, message: checker.message) do |corrector|
49
- corrector.replace(checker.node, checker.preferred_style)
72
+ add_offense(checker.offense_range,
73
+ message: checker.message) do |corrector|
74
+ corrector.replace(checker.offense_range, checker.prefer)
50
75
  end
51
76
  end
52
77
  end
@@ -59,13 +84,16 @@ module RuboCop
59
84
  SymbolicStyleChecker
60
85
  when :numeric
61
86
  NumericStyleChecker
87
+ when :be_status
88
+ BeStatusStyleChecker
62
89
  end
63
90
  end
64
91
 
65
92
  # :nodoc:
66
- class SymbolicStyleChecker
93
+ class StyleCheckerBase
67
94
  MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
68
95
  'to describe HTTP status code.'
96
+ ALLOWED_STATUSES = %i[error success missing redirect].freeze
69
97
 
70
98
  attr_reader :node
71
99
 
@@ -73,16 +101,36 @@ module RuboCop
73
101
  @node = node
74
102
  end
75
103
 
104
+ def message
105
+ format(MSG, prefer: prefer, current: current)
106
+ end
107
+
108
+ def offense_range
109
+ node
110
+ end
111
+
112
+ def allowed_symbol?
113
+ node.sym_type? && ALLOWED_STATUSES.include?(node.value)
114
+ end
115
+
116
+ def custom_http_status_code?
117
+ node.int_type? &&
118
+ !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i)
119
+ end
120
+ end
121
+
122
+ # :nodoc:
123
+ class SymbolicStyleChecker < StyleCheckerBase
76
124
  def offensive?
77
125
  !node.sym_type? && !custom_http_status_code?
78
126
  end
79
127
 
80
- def message
81
- format(MSG, prefer: preferred_style, current: number.to_s)
128
+ def prefer
129
+ symbol.inspect
82
130
  end
83
131
 
84
- def preferred_style
85
- symbol.inspect
132
+ def current
133
+ node.value.inspect
86
134
  end
87
135
 
88
136
  private
@@ -92,52 +140,77 @@ module RuboCop
92
140
  end
93
141
 
94
142
  def number
95
- node.source.to_i
96
- end
97
-
98
- def custom_http_status_code?
99
- node.int_type? &&
100
- !::Rack::Utils::SYMBOL_TO_STATUS_CODE.value?(node.source.to_i)
143
+ node.source.delete('"').to_i
101
144
  end
102
145
  end
103
146
 
104
147
  # :nodoc:
105
- class NumericStyleChecker
106
- MSG = 'Prefer `%<prefer>s` over `%<current>s` ' \
107
- 'to describe HTTP status code.'
148
+ class NumericStyleChecker < StyleCheckerBase
149
+ def offensive?
150
+ !node.int_type? && !allowed_symbol?
151
+ end
108
152
 
109
- ALLOWED_STATUSES = %i[error success missing redirect].freeze
153
+ def prefer
154
+ number.to_s
155
+ end
110
156
 
111
- attr_reader :node
157
+ def current
158
+ symbol.inspect
159
+ end
112
160
 
113
- def initialize(node)
114
- @node = node
161
+ private
162
+
163
+ def symbol
164
+ node.value
115
165
  end
116
166
 
167
+ def number
168
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol.to_sym]
169
+ end
170
+ end
171
+
172
+ # :nodoc:
173
+ class BeStatusStyleChecker < StyleCheckerBase
117
174
  def offensive?
118
- !node.int_type? && !allowed_symbol?
175
+ (!node.sym_type? && !custom_http_status_code?) ||
176
+ (!node.int_type? && !allowed_symbol?)
119
177
  end
120
178
 
121
- def message
122
- format(MSG, prefer: preferred_style, current: symbol.inspect)
179
+ def offense_range
180
+ node.parent
123
181
  end
124
182
 
125
- def preferred_style
126
- number.to_s
183
+ def prefer
184
+ if node.sym_type?
185
+ "be_#{node.value}"
186
+ elsif node.int_type?
187
+ "be_#{symbol}"
188
+ elsif node.str_type?
189
+ "be_#{normalize_str}"
190
+ end
191
+ end
192
+
193
+ def current
194
+ offense_range.source
127
195
  end
128
196
 
129
197
  private
130
198
 
131
- def number
132
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
199
+ def symbol
200
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(number)
133
201
  end
134
202
 
135
- def symbol
136
- node.value
203
+ def number
204
+ node.source.to_i
137
205
  end
138
206
 
139
- def allowed_symbol?
140
- node.sym_type? && ALLOWED_STATUSES.include?(node.value)
207
+ def normalize_str
208
+ normalized = node.source.delete('"')
209
+ if normalized.match?(/\A\d+\z/)
210
+ ::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(normalized.to_i)
211
+ else
212
+ normalized
213
+ end
141
214
  end
142
215
  end
143
216
  end
@@ -96,12 +96,12 @@ module RuboCop
96
96
  # @return [Parser::Source::Range]
97
97
  def remove_range(node)
98
98
  if node.left_sibling
99
- node.loc.expression.with(
100
- begin_pos: node.left_sibling.loc.expression.end_pos
99
+ node.source_range.with(
100
+ begin_pos: node.left_sibling.source_range.end_pos
101
101
  )
102
102
  elsif node.right_sibling
103
- node.loc.expression.with(
104
- end_pos: node.right_sibling.loc.expression.begin_pos
103
+ node.source_range.with(
104
+ end_pos: node.right_sibling.source_range.begin_pos
105
105
  )
106
106
  end
107
107
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Rails
7
+ # Check if using Minitest matchers.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # assert_equal(a, b)
12
+ # assert_equal a, b, "must be equal"
13
+ # refute_equal(a, b)
14
+ #
15
+ # # good
16
+ # expect(b).to eq(a)
17
+ # expect(b).to(eq(a), "must be equal")
18
+ # expect(b).not_to eq(a)
19
+ #
20
+ class MinitestAssertions < Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use `%<prefer>s`.'
24
+ RESTRICT_ON_SEND = %i[assert_equal refute_equal].freeze
25
+
26
+ # @!method minitest_assertion(node)
27
+ def_node_matcher :minitest_assertion, <<-PATTERN
28
+ (send nil? {:assert_equal :refute_equal} $_ $_ $_?)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ minitest_assertion(node) do |expected, actual, failure_message|
33
+ prefer = replacement(node, expected, actual,
34
+ failure_message.first)
35
+ add_offense(node, message: message(prefer)) do |corrector|
36
+ corrector.replace(node, prefer)
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def replacement(node, expected, actual, failure_message)
44
+ runner = node.method?(:assert_equal) ? 'to' : 'not_to'
45
+ if failure_message.nil?
46
+ "expect(#{actual.source}).#{runner} eq(#{expected.source})"
47
+ else
48
+ "expect(#{actual.source}).#{runner}(eq(#{expected.source}), " \
49
+ "#{failure_message.source})"
50
+ end
51
+ end
52
+
53
+ def message(prefer)
54
+ format(MSG, prefer: prefer)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,102 @@
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
+ # @safety
10
+ # This cop is unsafe because it cannot guarantee that
11
+ # the test target is an instance of `ActiveModel::Validations``.
12
+ #
13
+ # @example EnforcedStyle: not_to (default)
14
+ # # bad
15
+ # expect(foo).to be_invalid
16
+ #
17
+ # # good
18
+ # expect(foo).not_to be_valid
19
+ #
20
+ # # good (with method chain)
21
+ # expect(foo).to be_invalid.and be_odd
22
+ #
23
+ # @example EnforcedStyle: be_invalid
24
+ # # bad
25
+ # expect(foo).not_to be_valid
26
+ #
27
+ # # good
28
+ # expect(foo).to be_invalid
29
+ #
30
+ # # good (with method chain)
31
+ # expect(foo).to be_invalid.or be_even
32
+ #
33
+ class NegationBeValid < Base
34
+ extend AutoCorrector
35
+ include ConfigurableEnforcedStyle
36
+
37
+ MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
38
+ RESTRICT_ON_SEND = %i[be_valid be_invalid].freeze
39
+
40
+ # @!method not_to?(node)
41
+ def_node_matcher :not_to?, <<~PATTERN
42
+ (send ... :not_to (send nil? :be_valid ...))
43
+ PATTERN
44
+
45
+ # @!method be_invalid?(node)
46
+ def_node_matcher :be_invalid?, <<~PATTERN
47
+ (send ... :to (send nil? :be_invalid ...))
48
+ PATTERN
49
+
50
+ def on_send(node)
51
+ return unless offense?(node.parent)
52
+
53
+ add_offense(offense_range(node),
54
+ message: message(node.method_name)) do |corrector|
55
+ corrector.replace(node.parent.loc.selector, replaced_runner)
56
+ corrector.replace(node.loc.selector, replaced_matcher)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def offense?(node)
63
+ case style
64
+ when :not_to
65
+ be_invalid?(node)
66
+ when :be_invalid
67
+ not_to?(node)
68
+ end
69
+ end
70
+
71
+ def offense_range(node)
72
+ node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
73
+ end
74
+
75
+ def message(_matcher)
76
+ format(MSG,
77
+ runner: replaced_runner,
78
+ matcher: replaced_matcher)
79
+ end
80
+
81
+ def replaced_runner
82
+ case style
83
+ when :not_to
84
+ 'not_to'
85
+ when :be_invalid
86
+ 'to'
87
+ end
88
+ end
89
+
90
+ def replaced_matcher
91
+ case style
92
+ when :not_to
93
+ 'be_valid'
94
+ when :be_invalid
95
+ 'be_invalid'
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end