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,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Description should be descriptive.
7
+ #
8
+ # If example group or example contains only `execute string`, numbers
9
+ # and regular expressions, the description is not clear.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # describe `time` do
14
+ # # ...
15
+ # end
16
+ #
17
+ # # bad
18
+ # context /when foo/ do
19
+ # # ...
20
+ # end
21
+ #
22
+ # # bad
23
+ # it 10000 do
24
+ # # ...
25
+ # end
26
+ #
27
+ # # good
28
+ # describe Foo do
29
+ # # ...
30
+ # end
31
+ #
32
+ # # good
33
+ # describe '#foo' do
34
+ # # ...
35
+ # end
36
+ #
37
+ # # good
38
+ # context "when #{foo} is bar" do
39
+ # # ...
40
+ # end
41
+ #
42
+ # # good
43
+ # it 'does something' do
44
+ # # ...
45
+ # end
46
+ #
47
+ class UndescriptiveLiteralsDescription < Base
48
+ MSG = 'Description should be descriptive.'
49
+
50
+ # @!method example_groups_or_example?(node)
51
+ def_node_matcher :example_groups_or_example?, <<~PATTERN
52
+ (block (send #rspec? {#ExampleGroups.all #Examples.all} $_) ...)
53
+ PATTERN
54
+
55
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
56
+ example_groups_or_example?(node) do |arg|
57
+ add_offense(arg) if offense?(arg)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def offense?(node)
64
+ %i[xstr int regexp].include?(node.type)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for a specified error in checking raised errors.
7
+ #
8
+ # Enforces one of an Exception type, a string, or a regular
9
+ # expression to match against the exception message as a parameter
10
+ # to `raise_error`
11
+ #
12
+ # @example
13
+ # # bad
14
+ # expect {
15
+ # raise StandardError.new('error')
16
+ # }.to raise_error
17
+ #
18
+ # # good
19
+ # expect {
20
+ # raise StandardError.new('error')
21
+ # }.to raise_error(StandardError)
22
+ #
23
+ # expect {
24
+ # raise StandardError.new('error')
25
+ # }.to raise_error('error')
26
+ #
27
+ # expect {
28
+ # raise StandardError.new('error')
29
+ # }.to raise_error(/err/)
30
+ #
31
+ # expect { do_something }.not_to raise_error
32
+ #
33
+ class UnspecifiedException < Base
34
+ MSG = 'Specify the exception being captured'
35
+ RESTRICT_ON_SEND = %i[to].freeze
36
+
37
+ # @!method empty_raise_error_or_exception(node)
38
+ def_node_matcher :empty_raise_error_or_exception, <<~PATTERN
39
+ (send
40
+ (block
41
+ (send nil? :expect) ...)
42
+ :to
43
+ (send nil? {:raise_error :raise_exception})
44
+ )
45
+ PATTERN
46
+
47
+ def on_send(node)
48
+ return unless empty_exception_matcher?(node)
49
+
50
+ add_offense(node.children.last)
51
+ end
52
+
53
+ private
54
+
55
+ def empty_exception_matcher?(node)
56
+ empty_raise_error_or_exception(node) && !block_with_args?(node.parent)
57
+ end
58
+
59
+ def block_with_args?(node)
60
+ return false unless node&.block_type?
61
+
62
+ node.arguments?
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that memoized helpers names are symbols or strings.
7
+ #
8
+ # @example EnforcedStyle: symbols (default)
9
+ # # bad
10
+ # subject('user') { create_user }
11
+ # let('user_name') { 'Adam' }
12
+ #
13
+ # # good
14
+ # subject(:user) { create_user }
15
+ # let(:user_name) { 'Adam' }
16
+ #
17
+ # @example EnforcedStyle: strings
18
+ # # bad
19
+ # subject(:user) { create_user }
20
+ # let(:user_name) { 'Adam' }
21
+ #
22
+ # # good
23
+ # subject('user') { create_user }
24
+ # let('user_name') { 'Adam' }
25
+ #
26
+ class VariableDefinition < Base
27
+ extend AutoCorrector
28
+ include ConfigurableEnforcedStyle
29
+ include Variable
30
+ include InsideExampleGroup
31
+
32
+ MSG = 'Use %<style>s for variable names.'
33
+
34
+ def on_send(node)
35
+ return unless inside_example_group?(node)
36
+
37
+ variable_definition?(node) do |variable|
38
+ next unless style_offense?(variable)
39
+
40
+ add_offense(
41
+ variable,
42
+ message: format(MSG, style: style)
43
+ ) do |corrector|
44
+ corrector.replace(variable, correct_variable(variable))
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def correct_variable(variable)
52
+ case variable.type
53
+ when :dsym
54
+ variable.source[1..]
55
+ when :sym
56
+ variable.value.to_s.inspect
57
+ else
58
+ variable.value.to_sym.inspect
59
+ end
60
+ end
61
+
62
+ def style_offense?(variable)
63
+ (style == :symbols && string?(variable)) ||
64
+ (style == :strings && symbol?(variable))
65
+ end
66
+
67
+ def string?(node)
68
+ node.str_type?
69
+ end
70
+
71
+ def symbol?(node)
72
+ node.sym_type? || node.dsym_type?
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that memoized helper names use the configured style.
7
+ #
8
+ # Variables can be excluded from checking using the `AllowedPatterns`
9
+ # option.
10
+ #
11
+ # @example EnforcedStyle: snake_case (default)
12
+ # # bad
13
+ # subject(:userName1) { 'Adam' }
14
+ # let(:userName2) { 'Adam' }
15
+ #
16
+ # # good
17
+ # subject(:user_name_1) { 'Adam' }
18
+ # let(:user_name_2) { 'Adam' }
19
+ #
20
+ # @example EnforcedStyle: camelCase
21
+ # # bad
22
+ # subject(:user_name_1) { 'Adam' }
23
+ # let(:user_name_2) { 'Adam' }
24
+ #
25
+ # # good
26
+ # subject(:userName1) { 'Adam' }
27
+ # let(:userName2) { 'Adam' }
28
+ #
29
+ # @example AllowedPatterns configuration
30
+ # # rubocop.yml
31
+ # # RSpec/VariableName:
32
+ # # EnforcedStyle: snake_case
33
+ # # AllowedPatterns:
34
+ # # - ^userFood
35
+ #
36
+ # @example
37
+ # # okay because it matches the `^userFood` regex in `AllowedPatterns`
38
+ # subject(:userFood_1) { 'spaghetti' }
39
+ # let(:userFood_2) { 'fettuccine' }
40
+ #
41
+ class VariableName < Base
42
+ include ConfigurableNaming
43
+ include AllowedPattern
44
+ include Variable
45
+ include InsideExampleGroup
46
+
47
+ MSG = 'Use %<style>s for variable names.'
48
+
49
+ def on_send(node)
50
+ return unless inside_example_group?(node)
51
+
52
+ variable_definition?(node) do |variable|
53
+ return if variable.dstr_type? || variable.dsym_type?
54
+ return if matches_allowed_pattern?(variable.value)
55
+
56
+ check_name(node, variable.value, variable.source_range)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def message(style)
63
+ format(MSG, style: style)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for consistent verified double reference style.
7
+ #
8
+ # Only investigates references that are one of the supported styles.
9
+ #
10
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
11
+ #
12
+ # This cop can be configured in your configuration using the
13
+ # `EnforcedStyle` option and supports `--auto-gen-config`.
14
+ #
15
+ # @example `EnforcedStyle: constant` (default)
16
+ # # bad
17
+ # let(:foo) do
18
+ # instance_double('ClassName', method_name: 'returned_value')
19
+ # end
20
+ #
21
+ # # good
22
+ # let(:foo) do
23
+ # instance_double(ClassName, method_name: 'returned_value')
24
+ # end
25
+ #
26
+ # @example `EnforcedStyle: string`
27
+ # # bad
28
+ # let(:foo) do
29
+ # instance_double(ClassName, method_name: 'returned_value')
30
+ # end
31
+ #
32
+ # # good
33
+ # let(:foo) do
34
+ # instance_double('ClassName', method_name: 'returned_value')
35
+ # end
36
+ #
37
+ # @example Reference is not in the supported style list. No enforcement
38
+ #
39
+ # # good
40
+ # let(:foo) do
41
+ # instance_double(@klass, method_name: 'returned_value')
42
+ # end
43
+ class VerifiedDoubleReference < Base
44
+ extend AutoCorrector
45
+ include ConfigurableEnforcedStyle
46
+
47
+ MSG = 'Use a %<style>s class reference for verified doubles.'
48
+
49
+ RESTRICT_ON_SEND = Set[
50
+ :class_double,
51
+ :class_spy,
52
+ :instance_double,
53
+ :instance_spy,
54
+ :mock_model,
55
+ :object_double,
56
+ :object_spy,
57
+ :stub_model
58
+ ].freeze
59
+
60
+ REFERENCE_TYPE_STYLES = {
61
+ str: :string,
62
+ const: :constant
63
+ }.freeze
64
+
65
+ # @!method verified_double(node)
66
+ def_node_matcher :verified_double, <<~PATTERN
67
+ (send
68
+ nil?
69
+ RESTRICT_ON_SEND
70
+ $_class_reference
71
+ ...)
72
+ PATTERN
73
+
74
+ def on_send(node)
75
+ verified_double(node) do |class_reference|
76
+ break correct_style_detected unless opposing_style?(class_reference)
77
+
78
+ message = format(MSG, style: style)
79
+ expression = class_reference.source_range
80
+
81
+ add_offense(expression, message: message) do |corrector|
82
+ offense = class_reference.source
83
+ corrector.replace(expression, correct_style(offense))
84
+
85
+ opposite_style_detected
86
+ end
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def opposing_style?(class_reference)
93
+ class_reference_style = REFERENCE_TYPE_STYLES[class_reference.type]
94
+
95
+ # Only enforce supported styles
96
+ return false unless class_reference_style
97
+
98
+ class_reference_style != style
99
+ end
100
+
101
+ def correct_style(offense)
102
+ if style == :string
103
+ "'#{offense}'"
104
+ else
105
+ offense.gsub(/^['"]|['"]$/, '')
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -5,32 +5,46 @@ module RuboCop
5
5
  module RSpec
6
6
  # Prefer using verifying doubles over normal doubles.
7
7
  #
8
- # @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
8
+ # @see https://rspec.info/features/3-12/rspec-mocks/verifying-doubles
9
9
  #
10
10
  # @example
11
11
  # # bad
12
- # it '...' do
13
- # widget = double("Widget")
12
+ # let(:foo) do
13
+ # double(method_name: 'returned value')
14
+ # end
15
+ #
16
+ # # bad
17
+ # let(:foo) do
18
+ # double("ClassName", method_name: 'returned value')
14
19
  # end
15
20
  #
16
21
  # # good
17
- # it '...' do
18
- # widget = instance_double("Widget")
22
+ # let(:foo) do
23
+ # instance_double("ClassName", method_name: 'returned value')
19
24
  # end
20
- class VerifiedDoubles < Cop
21
- include RuboCop::RSpec::SpecOnly
22
-
23
- MSG = 'Prefer using verifying doubles over normal doubles.'.freeze
25
+ #
26
+ class VerifiedDoubles < Base
27
+ MSG = 'Prefer using verifying doubles over normal doubles.'
28
+ RESTRICT_ON_SEND = %i[double spy].freeze
24
29
 
25
- def_node_matcher :unverified_double, <<-PATTERN
26
- {(send nil {:double :spy} $_ ...) }
30
+ # @!method unverified_double(node)
31
+ def_node_matcher :unverified_double, <<~PATTERN
32
+ {(send nil? {:double :spy} $...)}
27
33
  PATTERN
28
34
 
29
35
  def on_send(node)
30
- return unless (name = unverified_double(node))
31
- return if name.type.equal?(:sym) && cop_config['IgnoreSymbolicNames']
36
+ unverified_double(node) do |name, *_args|
37
+ return if name.nil? && cop_config['IgnoreNameless']
38
+ return if symbol?(name) && cop_config['IgnoreSymbolicNames']
39
+
40
+ add_offense(node)
41
+ end
42
+ end
43
+
44
+ private
32
45
 
33
- add_offense(node, :expression)
46
+ def symbol?(name)
47
+ name&.sym_type?
34
48
  end
35
49
  end
36
50
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks void `expect()`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(something)
11
+ #
12
+ # # good
13
+ # expect(something).to be(1)
14
+ #
15
+ class VoidExpect < Base
16
+ MSG = 'Do not use `expect()` without `.to` or `.not_to`. ' \
17
+ 'Chain the methods or remove it.'
18
+ RESTRICT_ON_SEND = %i[expect].freeze
19
+
20
+ # @!method expect?(node)
21
+ def_node_matcher :expect?, <<~PATTERN
22
+ (send nil? :expect ...)
23
+ PATTERN
24
+
25
+ # @!method expect_block?(node)
26
+ def_node_matcher :expect_block?, <<~PATTERN
27
+ (block #expect? (args) _body)
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ return unless expect?(node)
32
+
33
+ check_expect(node)
34
+ end
35
+
36
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
37
+ return unless expect_block?(node)
38
+
39
+ check_expect(node)
40
+ end
41
+
42
+ private
43
+
44
+ def check_expect(node)
45
+ return unless void?(node)
46
+
47
+ add_offense(node)
48
+ end
49
+
50
+ def void?(expect)
51
+ parent = expect.parent
52
+ return true unless parent
53
+ return true if parent.begin_type?
54
+
55
+ parent.block_type? && parent.body == expect
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for calling a block within a stub.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # allow(foo).to receive(:bar) { |&block| block.call(1) }
11
+ #
12
+ # # good
13
+ # expect(foo).to receive(:bar).and_yield(1)
14
+ #
15
+ class Yield < Base
16
+ extend AutoCorrector
17
+ include RangeHelp
18
+
19
+ MSG = 'Use `.and_yield`.'
20
+
21
+ # @!method method_on_stub?(node)
22
+ def_node_search :method_on_stub?, '(send nil? :receive ...)'
23
+
24
+ # @!method block_arg(node)
25
+ def_node_matcher :block_arg, '(args (blockarg $_))'
26
+
27
+ # @!method block_call?(node)
28
+ def_node_matcher :block_call?, '(send (lvar %) :call ...)'
29
+
30
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
31
+ return unless method_on_stub?(node.send_node)
32
+
33
+ block_arg(node.arguments) do |block|
34
+ if calling_block?(node.body, block)
35
+ range = block_range(node)
36
+
37
+ add_offense(range) do |corrector|
38
+ autocorrect(corrector, node, range)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def autocorrect(corrector, node, range)
47
+ corrector.replace(
48
+ range_with_surrounding_space(range, side: :left),
49
+ generate_replacement(node.body)
50
+ )
51
+ end
52
+
53
+ def calling_block?(node, block)
54
+ if node.begin_type?
55
+ node.each_child_node.all? { |child| block_call?(child, block) }
56
+ else
57
+ block_call?(node, block)
58
+ end
59
+ end
60
+
61
+ def block_range(node)
62
+ node.loc.begin.with(end_pos: node.loc.end.end_pos)
63
+ end
64
+
65
+ def generate_replacement(node)
66
+ if node.begin_type?
67
+ node.children.map { |child| convert_block_to_yield(child) }.join
68
+ else
69
+ convert_block_to_yield(node)
70
+ end
71
+ end
72
+
73
+ def convert_block_to_yield(node)
74
+ args = node.arguments
75
+ replacement = '.and_yield'
76
+ replacement += "(#{args.map(&:source).join(', ')})" if args.any?
77
+ replacement
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end