rubocop-rspec 1.7.0 → 3.0.2

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 (193) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +955 -79
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/MIT-LICENSE.md +1 -2
  5. data/README.md +35 -35
  6. data/config/default.yml +940 -52
  7. data/config/obsoletion.yml +30 -0
  8. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +49 -0
  9. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +49 -0
  10. data/lib/rubocop/cop/rspec/any_instance.rb +10 -13
  11. data/lib/rubocop/cop/rspec/around_block.rb +97 -0
  12. data/lib/rubocop/cop/rspec/base.rb +26 -0
  13. data/lib/rubocop/cop/rspec/be.rb +39 -0
  14. data/lib/rubocop/cop/rspec/be_empty.rb +45 -0
  15. data/lib/rubocop/cop/rspec/be_eq.rb +47 -0
  16. data/lib/rubocop/cop/rspec/be_eql.rb +18 -15
  17. data/lib/rubocop/cop/rspec/be_nil.rb +74 -0
  18. data/lib/rubocop/cop/rspec/before_after_all.rb +45 -0
  19. data/lib/rubocop/cop/rspec/change_by_zero.rb +184 -0
  20. data/lib/rubocop/cop/rspec/class_check.rb +101 -0
  21. data/lib/rubocop/cop/rspec/contain_exactly.rb +56 -0
  22. data/lib/rubocop/cop/rspec/context_method.rb +57 -0
  23. data/lib/rubocop/cop/rspec/context_wording.rb +117 -0
  24. data/lib/rubocop/cop/rspec/describe_class.rb +52 -21
  25. data/lib/rubocop/cop/rspec/describe_method.rb +26 -11
  26. data/lib/rubocop/cop/rspec/describe_symbol.rb +37 -0
  27. data/lib/rubocop/cop/rspec/described_class.rb +181 -34
  28. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +38 -0
  29. data/lib/rubocop/cop/rspec/dialect.rb +84 -0
  30. data/lib/rubocop/cop/rspec/duplicated_metadata.rb +58 -0
  31. data/lib/rubocop/cop/rspec/empty_example_group.rb +134 -47
  32. data/lib/rubocop/cop/rspec/empty_hook.rb +49 -0
  33. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +82 -0
  34. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +42 -0
  35. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +40 -0
  36. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +82 -0
  37. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +36 -0
  38. data/lib/rubocop/cop/rspec/empty_metadata.rb +46 -0
  39. data/lib/rubocop/cop/rspec/empty_output.rb +47 -0
  40. data/lib/rubocop/cop/rspec/eq.rb +47 -0
  41. data/lib/rubocop/cop/rspec/example_length.rb +38 -20
  42. data/lib/rubocop/cop/rspec/example_without_description.rb +98 -0
  43. data/lib/rubocop/cop/rspec/example_wording.rb +117 -27
  44. data/lib/rubocop/cop/rspec/excessive_docstring_spacing.rb +110 -0
  45. data/lib/rubocop/cop/rspec/expect_actual.rb +46 -20
  46. data/lib/rubocop/cop/rspec/expect_change.rb +86 -0
  47. data/lib/rubocop/cop/rspec/expect_in_hook.rb +50 -0
  48. data/lib/rubocop/cop/rspec/expect_in_let.rb +42 -0
  49. data/lib/rubocop/cop/rspec/expect_output.rb +50 -0
  50. data/lib/rubocop/cop/rspec/focus.rb +79 -25
  51. data/lib/rubocop/cop/rspec/hook_argument.rb +48 -36
  52. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +81 -0
  53. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +37 -0
  54. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +68 -0
  55. data/lib/rubocop/cop/rspec/implicit_expect.rb +100 -0
  56. data/lib/rubocop/cop/rspec/implicit_subject.rb +167 -0
  57. data/lib/rubocop/cop/rspec/indexed_let.rb +112 -0
  58. data/lib/rubocop/cop/rspec/instance_spy.rb +74 -0
  59. data/lib/rubocop/cop/rspec/instance_variable.rb +28 -14
  60. data/lib/rubocop/cop/rspec/is_expected_specify.rb +45 -0
  61. data/lib/rubocop/cop/rspec/it_behaves_like.rb +49 -0
  62. data/lib/rubocop/cop/rspec/iterated_expectation.rb +74 -0
  63. data/lib/rubocop/cop/rspec/leading_subject.rb +57 -29
  64. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +127 -0
  65. data/lib/rubocop/cop/rspec/let_before_examples.rb +101 -0
  66. data/lib/rubocop/cop/rspec/let_setup.rb +32 -16
  67. data/lib/rubocop/cop/rspec/match_array.rb +59 -0
  68. data/lib/rubocop/cop/rspec/message_chain.rb +10 -15
  69. data/lib/rubocop/cop/rspec/message_expectation.rb +12 -9
  70. data/lib/rubocop/cop/rspec/message_spies.rb +88 -0
  71. data/lib/rubocop/cop/rspec/metadata_style.rb +202 -0
  72. data/lib/rubocop/cop/rspec/missing_example_group_argument.rb +35 -0
  73. data/lib/rubocop/cop/rspec/missing_expectation_target_method.rb +54 -0
  74. data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
  75. data/lib/rubocop/cop/rspec/mixin/empty_line_separation.rb +59 -0
  76. data/lib/rubocop/cop/rspec/mixin/file_help.rb +14 -0
  77. data/lib/rubocop/cop/rspec/mixin/final_end_location.rb +19 -0
  78. data/lib/rubocop/cop/rspec/mixin/inside_example_group.rb +29 -0
  79. data/lib/rubocop/cop/rspec/mixin/location_help.rb +37 -0
  80. data/lib/rubocop/cop/rspec/mixin/metadata.rb +63 -0
  81. data/lib/rubocop/cop/rspec/mixin/namespace.rb +23 -0
  82. data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +39 -0
  83. data/lib/rubocop/cop/rspec/mixin/top_level_group.rb +54 -0
  84. data/lib/rubocop/cop/rspec/mixin/variable.rb +21 -0
  85. data/lib/rubocop/cop/rspec/multiple_describes.rb +14 -12
  86. data/lib/rubocop/cop/rspec/multiple_expectations.rb +86 -26
  87. data/lib/rubocop/cop/rspec/multiple_memoized_helpers.rb +146 -0
  88. data/lib/rubocop/cop/rspec/multiple_subjects.rb +97 -0
  89. data/lib/rubocop/cop/rspec/named_subject.rb +107 -27
  90. data/lib/rubocop/cop/rspec/nested_groups.rb +84 -47
  91. data/lib/rubocop/cop/rspec/no_expectation_example.rb +102 -0
  92. data/lib/rubocop/cop/rspec/not_to_not.rb +30 -27
  93. data/lib/rubocop/cop/rspec/overwriting_setup.rb +74 -0
  94. data/lib/rubocop/cop/rspec/pending.rb +80 -0
  95. data/lib/rubocop/cop/rspec/pending_without_reason.rb +159 -0
  96. data/lib/rubocop/cop/rspec/predicate_matcher.rb +341 -0
  97. data/lib/rubocop/cop/rspec/receive_counts.rb +89 -0
  98. data/lib/rubocop/cop/rspec/receive_messages.rb +161 -0
  99. data/lib/rubocop/cop/rspec/receive_never.rb +41 -0
  100. data/lib/rubocop/cop/rspec/redundant_around.rb +65 -0
  101. data/lib/rubocop/cop/rspec/redundant_predicate_matcher.rb +67 -0
  102. data/lib/rubocop/cop/rspec/remove_const.rb +39 -0
  103. data/lib/rubocop/cop/rspec/repeated_description.rb +98 -0
  104. data/lib/rubocop/cop/rspec/repeated_example.rb +53 -0
  105. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +100 -0
  106. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
  107. data/lib/rubocop/cop/rspec/repeated_include_example.rb +105 -0
  108. data/lib/rubocop/cop/rspec/repeated_subject_call.rb +125 -0
  109. data/lib/rubocop/cop/rspec/return_from_stub.rb +169 -0
  110. data/lib/rubocop/cop/rspec/scattered_let.rb +59 -0
  111. data/lib/rubocop/cop/rspec/scattered_setup.rb +92 -0
  112. data/lib/rubocop/cop/rspec/shared_context.rb +107 -0
  113. data/lib/rubocop/cop/rspec/shared_examples.rb +125 -0
  114. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +93 -0
  115. data/lib/rubocop/cop/rspec/skip_block_inside_example.rb +46 -0
  116. data/lib/rubocop/cop/rspec/sort_metadata.rb +71 -0
  117. data/lib/rubocop/cop/rspec/spec_file_path_format.rb +133 -0
  118. data/lib/rubocop/cop/rspec/spec_file_path_suffix.rb +40 -0
  119. data/lib/rubocop/cop/rspec/stubbed_mock.rb +176 -0
  120. data/lib/rubocop/cop/rspec/subject_declaration.rb +46 -0
  121. data/lib/rubocop/cop/rspec/subject_stub.rb +93 -74
  122. data/lib/rubocop/cop/rspec/undescriptive_literals_description.rb +69 -0
  123. data/lib/rubocop/cop/rspec/unspecified_exception.rb +67 -0
  124. data/lib/rubocop/cop/rspec/variable_definition.rb +77 -0
  125. data/lib/rubocop/cop/rspec/variable_name.rb +68 -0
  126. data/lib/rubocop/cop/rspec/verified_double_reference.rb +111 -0
  127. data/lib/rubocop/cop/rspec/verified_doubles.rb +28 -14
  128. data/lib/rubocop/cop/rspec/void_expect.rb +60 -0
  129. data/lib/rubocop/cop/rspec/yield.rb +82 -0
  130. data/lib/rubocop/cop/rspec_cops.rb +112 -0
  131. data/lib/rubocop/rspec/align_let_brace.rb +63 -0
  132. data/lib/rubocop/rspec/concept.rb +33 -0
  133. data/lib/rubocop/rspec/config_formatter.rb +27 -4
  134. data/lib/rubocop/rspec/cop/generator.rb +25 -0
  135. data/lib/rubocop/rspec/corrector/move_node.rb +51 -0
  136. data/lib/rubocop/rspec/description_extractor.rb +60 -18
  137. data/lib/rubocop/rspec/example.rb +37 -0
  138. data/lib/rubocop/rspec/example_group.rb +67 -0
  139. data/lib/rubocop/rspec/hook.rb +79 -0
  140. data/lib/rubocop/rspec/inject.rb +3 -1
  141. data/lib/rubocop/rspec/language.rb +184 -41
  142. data/lib/rubocop/rspec/node.rb +19 -0
  143. data/lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb +29 -0
  144. data/lib/rubocop/rspec/version.rb +1 -1
  145. data/lib/rubocop/rspec/wording.rb +61 -19
  146. data/lib/rubocop/rspec.rb +6 -2
  147. data/lib/rubocop-rspec.rb +45 -34
  148. metadata +130 -195
  149. data/Gemfile +0 -13
  150. data/Rakefile +0 -48
  151. data/lib/rubocop/cop/rspec/file_path.rb +0 -83
  152. data/lib/rubocop/rspec/language/node_pattern.rb +0 -16
  153. data/lib/rubocop/rspec/spec_only.rb +0 -61
  154. data/lib/rubocop/rspec/top_level_describe.rb +0 -61
  155. data/lib/rubocop/rspec/util.rb +0 -19
  156. data/rubocop-rspec.gemspec +0 -42
  157. data/spec/expect_violation/expectation_spec.rb +0 -85
  158. data/spec/project/changelog_spec.rb +0 -81
  159. data/spec/project/default_config_spec.rb +0 -52
  160. data/spec/project/project_requires_spec.rb +0 -8
  161. data/spec/rubocop/cop/rspec/any_instance_spec.rb +0 -30
  162. data/spec/rubocop/cop/rspec/be_eql_spec.rb +0 -59
  163. data/spec/rubocop/cop/rspec/describe_class_spec.rb +0 -113
  164. data/spec/rubocop/cop/rspec/describe_method_spec.rb +0 -32
  165. data/spec/rubocop/cop/rspec/described_class_spec.rb +0 -219
  166. data/spec/rubocop/cop/rspec/empty_example_group_spec.rb +0 -79
  167. data/spec/rubocop/cop/rspec/example_length_spec.rb +0 -117
  168. data/spec/rubocop/cop/rspec/example_wording_spec.rb +0 -82
  169. data/spec/rubocop/cop/rspec/expect_actual_spec.rb +0 -136
  170. data/spec/rubocop/cop/rspec/file_path_spec.rb +0 -236
  171. data/spec/rubocop/cop/rspec/focus_spec.rb +0 -130
  172. data/spec/rubocop/cop/rspec/hook_argument_spec.rb +0 -189
  173. data/spec/rubocop/cop/rspec/instance_variable_spec.rb +0 -75
  174. data/spec/rubocop/cop/rspec/leading_subject_spec.rb +0 -54
  175. data/spec/rubocop/cop/rspec/let_setup_spec.rb +0 -66
  176. data/spec/rubocop/cop/rspec/message_chain_spec.rb +0 -21
  177. data/spec/rubocop/cop/rspec/message_expectation_spec.rb +0 -63
  178. data/spec/rubocop/cop/rspec/multiple_describes_spec.rb +0 -28
  179. data/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +0 -84
  180. data/spec/rubocop/cop/rspec/named_subject_spec.rb +0 -62
  181. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +0 -55
  182. data/spec/rubocop/cop/rspec/not_to_not_spec.rb +0 -57
  183. data/spec/rubocop/cop/rspec/subject_stub_spec.rb +0 -183
  184. data/spec/rubocop/cop/rspec/verified_doubles_spec.rb +0 -71
  185. data/spec/rubocop/rspec/config_formatter_spec.rb +0 -48
  186. data/spec/rubocop/rspec/description_extractor_spec.rb +0 -35
  187. data/spec/rubocop/rspec/language/selector_set_spec.rb +0 -29
  188. data/spec/rubocop/rspec/spec_only_spec.rb +0 -97
  189. data/spec/rubocop/rspec/util/one_spec.rb +0 -21
  190. data/spec/rubocop/rspec/wording_spec.rb +0 -44
  191. data/spec/shared/rspec_only_cop_behavior.rb +0 -68
  192. data/spec/spec_helper.rb +0 -41
  193. data/spec/support/expect_violation.rb +0 -166
@@ -0,0 +1,341 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # A helper for `inflected` style
7
+ module InflectedHelper
8
+ include RuboCop::RSpec::Language
9
+ extend NodePattern::Macros
10
+
11
+ MSG_INFLECTED = 'Prefer using `%<matcher_name>s` matcher over ' \
12
+ '`%<predicate_name>s`.'
13
+
14
+ private
15
+
16
+ def check_inflected(node)
17
+ predicate_in_actual?(node) do |predicate, to, matcher|
18
+ msg = message_inflected(predicate)
19
+ add_offense(node, message: msg) do |corrector|
20
+ remove_predicate(corrector, predicate)
21
+ corrector.replace(node.loc.selector,
22
+ true?(to, matcher) ? 'to' : 'not_to')
23
+ rewrite_matcher(corrector, predicate, matcher)
24
+ end
25
+ end
26
+ end
27
+
28
+ # @!method predicate_in_actual?(node)
29
+ def_node_matcher :predicate_in_actual?, <<~PATTERN
30
+ (send
31
+ (send nil? :expect {
32
+ (block $(send !nil? #predicate? ...) ...)
33
+ $(send !nil? #predicate? ...)})
34
+ $#Runners.all
35
+ $#boolean_matcher? ...)
36
+ PATTERN
37
+
38
+ # @!method be_bool?(node)
39
+ def_node_matcher :be_bool?, <<~PATTERN
40
+ (send nil? {:be :eq :eql :equal} {true false})
41
+ PATTERN
42
+
43
+ # @!method be_boolthy?(node)
44
+ def_node_matcher :be_boolthy?, <<~PATTERN
45
+ (send nil? {:be_truthy :be_falsey :be_falsy :a_truthy_value :a_falsey_value :a_falsy_value})
46
+ PATTERN
47
+
48
+ def boolean_matcher?(node)
49
+ if cop_config['Strict']
50
+ be_boolthy?(node)
51
+ else
52
+ be_bool?(node) || be_boolthy?(node)
53
+ end
54
+ end
55
+
56
+ def predicate?(sym)
57
+ sym.to_s.end_with?('?')
58
+ end
59
+
60
+ def message_inflected(predicate)
61
+ format(MSG_INFLECTED,
62
+ predicate_name: predicate.method_name,
63
+ matcher_name: to_predicate_matcher(predicate.method_name))
64
+ end
65
+
66
+ # rubocop:disable Metrics/MethodLength
67
+ def to_predicate_matcher(name)
68
+ case name = name.to_s
69
+ when 'is_a?'
70
+ 'be_a'
71
+ when 'instance_of?'
72
+ 'be_an_instance_of'
73
+ when 'include?', 'respond_to?'
74
+ name[0..-2]
75
+ when 'exist?', 'exists?'
76
+ 'exist'
77
+ when /\Ahas_/
78
+ name.sub('has_', 'have_')[0..-2]
79
+ else
80
+ "be_#{name[0..-2]}"
81
+ end
82
+ end
83
+ # rubocop:enable Metrics/MethodLength
84
+
85
+ def remove_predicate(corrector, predicate)
86
+ range = predicate.loc.dot.with(
87
+ end_pos: predicate.source_range.end_pos
88
+ )
89
+
90
+ corrector.remove(range)
91
+
92
+ block_range = LocationHelp.block_with_whitespace(predicate)
93
+ corrector.remove(block_range) if block_range
94
+ end
95
+
96
+ def rewrite_matcher(corrector, predicate, matcher)
97
+ args = LocationHelp.arguments_with_whitespace(predicate).source
98
+ block_loc = LocationHelp.block_with_whitespace(predicate)
99
+ block = block_loc ? block_loc.source : ''
100
+
101
+ corrector.replace(
102
+ matcher,
103
+ to_predicate_matcher(predicate.method_name) + args + block
104
+ )
105
+ end
106
+
107
+ def true?(to_symbol, matcher)
108
+ result = case matcher.method_name
109
+ when :be, :eq
110
+ matcher.first_argument.true_type?
111
+ when :be_truthy, :a_truthy_value
112
+ true
113
+ when :be_falsey, :be_falsy, :a_falsey_value, :a_falsy_value
114
+ false
115
+ end
116
+ to_symbol == :to ? result : !result
117
+ end
118
+ end
119
+
120
+ # A helper for `explicit` style
121
+ module ExplicitHelper # rubocop:disable Metrics/ModuleLength
122
+ include RuboCop::RSpec::Language
123
+ extend NodePattern::Macros
124
+
125
+ MSG_EXPLICIT = 'Prefer using `%<predicate_name>s` over ' \
126
+ '`%<matcher_name>s` matcher.'
127
+ BUILT_IN_MATCHERS = %w[
128
+ be_truthy be_falsey be_falsy
129
+ have_attributes have_received
130
+ be_between be_within
131
+ ].freeze
132
+
133
+ private
134
+
135
+ def allowed_explicit_matchers
136
+ cop_config.fetch('AllowedExplicitMatchers', []) + BUILT_IN_MATCHERS
137
+ end
138
+
139
+ def check_explicit(node) # rubocop:disable Metrics/MethodLength
140
+ predicate_matcher_block?(node) do |actual, matcher|
141
+ add_offense(node, message: message_explicit(matcher)) do |corrector|
142
+ to_node = node.send_node
143
+ corrector_explicit(corrector, to_node, actual, matcher, to_node)
144
+ end
145
+ ignore_node(node.children.first)
146
+ return
147
+ end
148
+
149
+ return if part_of_ignored_node?(node)
150
+
151
+ predicate_matcher?(node) do |actual, matcher|
152
+ next unless replaceable_matcher?(matcher)
153
+
154
+ add_offense(node, message: message_explicit(matcher)) do |corrector|
155
+ next if uncorrectable_matcher?(node, matcher)
156
+
157
+ corrector_explicit(corrector, node, actual, matcher, matcher)
158
+ end
159
+ end
160
+ end
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
+ arg.str_type? || arg.dstr_type? || arg.xstr_type?
178
+ end.any?(&:heredoc?)
179
+ end
180
+
181
+ # @!method predicate_matcher?(node)
182
+ def_node_matcher :predicate_matcher?, <<~PATTERN
183
+ (send
184
+ (send nil? :expect $!nil?)
185
+ #Runners.all
186
+ {
187
+ $(send nil? #predicate_matcher_name? ...)
188
+ (block $(send nil? #predicate_matcher_name? ...) ...)
189
+ }
190
+ ...
191
+ )
192
+ PATTERN
193
+
194
+ # @!method predicate_matcher_block?(node)
195
+ def_node_matcher :predicate_matcher_block?, <<~PATTERN
196
+ (block
197
+ (send
198
+ (send nil? :expect $!nil?)
199
+ #Runners.all
200
+ $(send nil? #predicate_matcher_name?))
201
+ ...)
202
+ PATTERN
203
+
204
+ def predicate_matcher_name?(name)
205
+ name = name.to_s
206
+
207
+ return false if allowed_explicit_matchers.include?(name)
208
+
209
+ (name.start_with?('be_', 'have_') && !name.end_with?('?')) ||
210
+ %w[include respond_to].include?(name)
211
+ end
212
+
213
+ def message_explicit(matcher)
214
+ format(MSG_EXPLICIT,
215
+ predicate_name: to_predicate_method(matcher.method_name),
216
+ matcher_name: matcher.method_name)
217
+ end
218
+
219
+ def corrector_explicit(corrector, to_node, actual, matcher, block_child)
220
+ replacement_matcher = replacement_matcher(to_node)
221
+ corrector.replace(matcher, replacement_matcher)
222
+ move_predicate(corrector, actual, matcher, block_child)
223
+ corrector.replace(to_node.loc.selector, 'to')
224
+ end
225
+
226
+ def move_predicate(corrector, actual, matcher, block_child)
227
+ predicate = to_predicate_method(matcher.method_name)
228
+ args = LocationHelp.arguments_with_whitespace(matcher).source
229
+ block_loc = LocationHelp.block_with_whitespace(block_child)
230
+ block = block_loc ? block_loc.source : ''
231
+
232
+ corrector.remove(block_loc) if block_loc
233
+ corrector.insert_after(actual, ".#{predicate}" + args + block)
234
+ end
235
+
236
+ # rubocop:disable Metrics/MethodLength
237
+ def to_predicate_method(matcher)
238
+ case matcher = matcher.to_s
239
+ when 'be_a', 'be_an', 'be_a_kind_of', 'a_kind_of', 'be_kind_of'
240
+ 'is_a?'
241
+ when 'be_an_instance_of', 'be_instance_of', 'an_instance_of'
242
+ 'instance_of?'
243
+ when 'include'
244
+ 'include?'
245
+ when 'respond_to'
246
+ 'respond_to?'
247
+ when /\Ahave_(.+)/
248
+ "has_#{Regexp.last_match(1)}?"
249
+ else
250
+ "#{matcher[/\Abe_(.+)/, 1]}?"
251
+ end
252
+ end
253
+ # rubocop:enable Metrics/MethodLength
254
+
255
+ def replacement_matcher(node)
256
+ case [cop_config['Strict'], node.method?(:to)]
257
+ when [true, true]
258
+ 'be(true)'
259
+ when [true, false]
260
+ 'be(false)'
261
+ when [false, true]
262
+ 'be_truthy'
263
+ when [false, false]
264
+ 'be_falsey'
265
+ end
266
+ end
267
+ end
268
+
269
+ # Prefer using predicate matcher over using predicate method directly.
270
+ #
271
+ # RSpec defines magic matchers for predicate methods.
272
+ # This cop recommends to use the predicate matcher instead of using
273
+ # predicate method directly.
274
+ #
275
+ # @example Strict: true, EnforcedStyle: inflected (default)
276
+ # # bad
277
+ # expect(foo.something?).to be_truthy
278
+ #
279
+ # # good
280
+ # expect(foo).to be_something
281
+ #
282
+ # # also good - It checks "true" strictly.
283
+ # expect(foo.something?).to be(true)
284
+ #
285
+ # @example Strict: false, EnforcedStyle: inflected
286
+ # # bad
287
+ # expect(foo.something?).to be_truthy
288
+ # expect(foo.something?).to be(true)
289
+ #
290
+ # # good
291
+ # expect(foo).to be_something
292
+ #
293
+ # @example Strict: true, EnforcedStyle: explicit
294
+ # # bad
295
+ # expect(foo).to be_something
296
+ #
297
+ # # good - the above code is rewritten to it by this cop
298
+ # expect(foo.something?).to be(true)
299
+ #
300
+ # # bad - no autocorrect
301
+ # expect(foo)
302
+ # .to be_something(<<~TEXT)
303
+ # bar
304
+ # TEXT
305
+ #
306
+ # # good
307
+ # expect(foo.something?(<<~TEXT)).to be(true)
308
+ # bar
309
+ # TEXT
310
+ #
311
+ # @example Strict: false, EnforcedStyle: explicit
312
+ # # bad
313
+ # expect(foo).to be_something
314
+ #
315
+ # # good - the above code is rewritten to it by this cop
316
+ # expect(foo.something?).to be_truthy
317
+ #
318
+ class PredicateMatcher < Base
319
+ extend AutoCorrector
320
+ include ConfigurableEnforcedStyle
321
+ include InflectedHelper
322
+ include ExplicitHelper
323
+
324
+ RESTRICT_ON_SEND = Runners.all
325
+
326
+ def on_send(node)
327
+ case style
328
+ when :inflected
329
+ check_inflected(node)
330
+ when :explicit
331
+ check_explicit(node)
332
+ end
333
+ end
334
+
335
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
336
+ check_explicit(node) if style == :explicit
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for `once` and `twice` receive counts matchers usage.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(foo).to receive(:bar).exactly(1).times
11
+ # expect(foo).to receive(:bar).exactly(2).times
12
+ # expect(foo).to receive(:bar).at_least(1).times
13
+ # expect(foo).to receive(:bar).at_least(2).times
14
+ # expect(foo).to receive(:bar).at_most(1).times
15
+ # expect(foo).to receive(:bar).at_most(2).times
16
+ #
17
+ # # good
18
+ # expect(foo).to receive(:bar).once
19
+ # expect(foo).to receive(:bar).twice
20
+ # expect(foo).to receive(:bar).at_least(:once)
21
+ # expect(foo).to receive(:bar).at_least(:twice)
22
+ # expect(foo).to receive(:bar).at_most(:once)
23
+ # expect(foo).to receive(:bar).at_most(:twice).times
24
+ #
25
+ class ReceiveCounts < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Use `%<alternative>s` instead of `%<original>s`.'
29
+
30
+ RESTRICT_ON_SEND = %i[times].freeze
31
+
32
+ # @!method receive_counts(node)
33
+ def_node_matcher :receive_counts, <<~PATTERN
34
+ (send $(send _ {:exactly :at_least :at_most} (int {1 2})) :times)
35
+ PATTERN
36
+
37
+ # @!method stub?(node)
38
+ def_node_search :stub?, '(send nil? :receive ...)'
39
+
40
+ def on_send(node)
41
+ receive_counts(node) do |offending_node|
42
+ return unless stub?(offending_node.receiver)
43
+
44
+ offending_range = range(node, offending_node)
45
+
46
+ msg = message_for(offending_node, offending_range.source)
47
+ add_offense(offending_range, message: msg) do |corrector|
48
+ autocorrect(corrector, offending_node, offending_range)
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def autocorrect(corrector, node, range)
56
+ replacement = matcher_for(
57
+ node.method_name,
58
+ node.first_argument.source.to_i
59
+ )
60
+
61
+ corrector.replace(range, replacement)
62
+ end
63
+
64
+ def message_for(node, source)
65
+ alternative = matcher_for(
66
+ node.method_name,
67
+ node.first_argument.source.to_i
68
+ )
69
+ format(MSG, alternative: alternative, original: source)
70
+ end
71
+
72
+ def matcher_for(method, count)
73
+ matcher = count == 1 ? 'once' : 'twice'
74
+ if method == :exactly
75
+ ".#{matcher}"
76
+ else
77
+ ".#{method}(:#{matcher})"
78
+ end
79
+ end
80
+
81
+ def range(node, offending_node)
82
+ offending_node.loc.dot.with(
83
+ end_pos: node.source_range.end_pos
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for multiple messages stubbed on the same object.
7
+ #
8
+ # @safety
9
+ # The autocorrection is marked as unsafe, because it may change the
10
+ # order of stubs. This in turn may cause e.g. variables to be called
11
+ # before they are defined.
12
+ #
13
+ # @example
14
+ # # bad
15
+ # before do
16
+ # allow(Service).to receive(:foo).and_return(bar)
17
+ # allow(Service).to receive(:baz).and_return(qux)
18
+ # end
19
+ #
20
+ # # good
21
+ # before do
22
+ # allow(Service).to receive_messages(foo: bar, baz: qux)
23
+ # end
24
+ #
25
+ # # good - ignore same message
26
+ # before do
27
+ # allow(Service).to receive(:foo).and_return(bar)
28
+ # allow(Service).to receive(:foo).and_return(qux)
29
+ # end
30
+ #
31
+ class ReceiveMessages < Base
32
+ extend AutoCorrector
33
+ include RangeHelp
34
+
35
+ MSG = 'Use `receive_messages` instead of multiple stubs on lines ' \
36
+ '%<loc>s.'
37
+
38
+ # @!method allow_receive_message?(node)
39
+ def_node_matcher :allow_receive_message?, <<~PATTERN
40
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym _)) :and_return !#heredoc_or_splat?))
41
+ PATTERN
42
+
43
+ # @!method allow_argument(node)
44
+ def_node_matcher :allow_argument, <<~PATTERN
45
+ (send (send nil? :allow $_ ...) ...)
46
+ PATTERN
47
+
48
+ # @!method receive_node(node)
49
+ def_node_search :receive_node, <<~PATTERN
50
+ $(send (send nil? :receive ...) ...)
51
+ PATTERN
52
+
53
+ # @!method receive_arg(node)
54
+ def_node_search :receive_arg, <<~PATTERN
55
+ (send (send nil? :receive $_) ...)
56
+ PATTERN
57
+
58
+ # @!method receive_and_return_argument(node)
59
+ def_node_matcher :receive_and_return_argument, <<~PATTERN
60
+ (send (send nil? :allow ...) :to (send (send nil? :receive (sym $_)) :and_return $_))
61
+ PATTERN
62
+
63
+ def on_begin(node)
64
+ repeated_receive_message(node).each do |item, repeated_lines, args|
65
+ next if repeated_lines.empty?
66
+
67
+ register_offense(item, repeated_lines, args)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def repeated_receive_message(node)
74
+ node
75
+ .children
76
+ .select { |child| allow_receive_message?(child) }
77
+ .group_by { |child| allow_argument(child) }
78
+ .values
79
+ .reject(&:one?)
80
+ .flat_map { |items| add_repeated_lines_and_arguments(items) }
81
+ end
82
+
83
+ def add_repeated_lines_and_arguments(items)
84
+ uniq_items = uniq_items(items)
85
+ repeated_lines = uniq_items.map(&:first_line)
86
+ uniq_items.map do |item|
87
+ [item, repeated_lines - [item.first_line], arguments(uniq_items)]
88
+ end
89
+ end
90
+
91
+ def uniq_items(items)
92
+ items.select do |item|
93
+ items.none? do |i|
94
+ receive_arg(item).first == receive_arg(i).first &&
95
+ !same_line?(item, i)
96
+ end
97
+ end
98
+ end
99
+
100
+ def arguments(items)
101
+ items.map do |item|
102
+ receive_and_return_argument(item) do |receive_arg, return_arg|
103
+ "#{normalize_receive_arg(receive_arg)}: " \
104
+ "#{normalize_return_arg(return_arg)}"
105
+ end
106
+ end
107
+ end
108
+
109
+ def normalize_receive_arg(receive_arg)
110
+ if requires_quotes?(receive_arg)
111
+ "'#{receive_arg}'"
112
+ else
113
+ receive_arg
114
+ end
115
+ end
116
+
117
+ def normalize_return_arg(return_arg)
118
+ if return_arg.hash_type? && !return_arg.braces?
119
+ "{ #{return_arg.source} }"
120
+ else
121
+ return_arg.source
122
+ end
123
+ end
124
+
125
+ def register_offense(item, repeated_lines, args)
126
+ add_offense(item, message: message(repeated_lines)) do |corrector|
127
+ if item.loc.line > repeated_lines.max
128
+ replace_to_receive_messages(corrector, item, args)
129
+ else
130
+ corrector.remove(item_range_by_whole_lines(item))
131
+ end
132
+ end
133
+ end
134
+
135
+ def message(repeated_lines)
136
+ format(MSG, loc: repeated_lines)
137
+ end
138
+
139
+ def replace_to_receive_messages(corrector, item, args)
140
+ receive_node(item) do |node|
141
+ corrector.replace(node,
142
+ "receive_messages(#{args.join(', ')})")
143
+ end
144
+ end
145
+
146
+ def item_range_by_whole_lines(item)
147
+ range_by_whole_lines(item.source_range, include_final_newline: true)
148
+ end
149
+
150
+ def heredoc_or_splat?(node)
151
+ ((node.str_type? || node.dstr_type?) && node.heredoc?) ||
152
+ node.splat_type?
153
+ end
154
+
155
+ def requires_quotes?(value)
156
+ value.match?(/^:".*?"|=$|^\W+$/)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Prefer `not_to receive(...)` over `receive(...).never`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(foo).to receive(:bar).never
11
+ #
12
+ # # good
13
+ # expect(foo).not_to receive(:bar)
14
+ #
15
+ class ReceiveNever < Base
16
+ extend AutoCorrector
17
+ MSG = 'Use `not_to receive` instead of `never`.'
18
+ RESTRICT_ON_SEND = %i[never].freeze
19
+
20
+ # @!method method_on_stub?(node)
21
+ def_node_search :method_on_stub?, '(send nil? :receive ...)'
22
+
23
+ def on_send(node)
24
+ return unless node.method?(:never) && method_on_stub?(node)
25
+
26
+ add_offense(node.loc.selector) do |corrector|
27
+ autocorrect(corrector, node)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def autocorrect(corrector, node)
34
+ corrector.replace(node.parent.loc.selector, 'not_to')
35
+ range = node.loc.dot.with(end_pos: node.loc.selector.end_pos)
36
+ corrector.remove(range)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Remove redundant `around` hook.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # around do |example|
11
+ # example.run
12
+ # end
13
+ #
14
+ # # good
15
+ #
16
+ class RedundantAround < Base
17
+ extend AutoCorrector
18
+
19
+ MSG = 'Remove redundant `around` hook.'
20
+
21
+ RESTRICT_ON_SEND = %i[around].freeze
22
+
23
+ def on_block(node)
24
+ return unless match_redundant_around_hook_block?(node)
25
+
26
+ add_offense(node) do |corrector|
27
+ autocorrect(corrector, node)
28
+ end
29
+ end
30
+ alias on_numblock on_block
31
+
32
+ def on_send(node)
33
+ return unless match_redundant_around_hook_send?(node)
34
+
35
+ add_offense(node) do |corrector|
36
+ autocorrect(corrector, node)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # @!method match_redundant_around_hook_block?(node)
43
+ def_node_matcher :match_redundant_around_hook_block?, <<~PATTERN
44
+ ({block numblock} (send _ :around ...) ... (send _ :run))
45
+ PATTERN
46
+
47
+ # @!method match_redundant_around_hook_send?(node)
48
+ def_node_matcher :match_redundant_around_hook_send?, <<~PATTERN
49
+ (send
50
+ _
51
+ :around
52
+ ...
53
+ (block-pass
54
+ (sym :run)
55
+ )
56
+ )
57
+ PATTERN
58
+
59
+ def autocorrect(corrector, node)
60
+ corrector.remove(node)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end