rubocop-rspec 2.16.0 → 2.24.1

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