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
@@ -3,55 +3,83 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks for `subject` definitions that come after `let` definitions.
6
+ # Enforce that subject is the first definition in the test.
7
7
  #
8
8
  # @example
9
9
  # # bad
10
- # RSpec.describe User do
11
- # let(:params) { blah }
12
- # subject { described_class.new(params) }
10
+ # let(:params) { blah }
11
+ # subject { described_class.new(params) }
13
12
  #
14
- # it 'is valid' do
15
- # expect(subject.valid?).to be(true)
16
- # end
17
- # end
13
+ # before { do_something }
14
+ # subject { described_class.new(params) }
15
+ #
16
+ # it { expect_something }
17
+ # subject { described_class.new(params) }
18
+ # it { expect_something_else }
19
+ #
20
+ #
21
+ # # good
22
+ # subject { described_class.new(params) }
23
+ # let(:params) { blah }
18
24
  #
19
25
  # # good
20
- # RSpec.describe User do
21
- # subject { described_class.new(params) }
26
+ # subject { described_class.new(params) }
27
+ # before { do_something }
22
28
  #
23
- # let(:params) { blah }
29
+ # # good
30
+ # subject { described_class.new(params) }
31
+ # it { expect_something }
32
+ # it { expect_something_else }
24
33
  #
25
- # it 'is valid' do
26
- # expect(subject.valid?).to be(true)
27
- # end
28
- # end
29
- class LeadingSubject < Cop
30
- include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::Language
34
+ class LeadingSubject < Base
35
+ extend AutoCorrector
36
+ include InsideExampleGroup
31
37
 
32
- MSG = 'Declare `subject` above any other `let` declarations'.freeze
38
+ MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
33
39
 
34
- def_node_matcher :subject?, '(block $(send nil :subject ...) args ...)'
40
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
41
+ return unless subject?(node)
42
+ return unless inside_example_group?(node)
35
43
 
36
- def on_block(node)
37
- return unless subject?(node) && !in_spec_block?(node)
44
+ check_previous_nodes(node)
45
+ end
38
46
 
39
- node.parent.each_child_node do |sibling|
40
- break if sibling.equal?(node)
47
+ private
41
48
 
42
- if sibling.method_name.equal?(:let)
43
- break add_offense(node, :expression)
49
+ def check_previous_nodes(node)
50
+ offending_node(node) do |offender|
51
+ msg = format(MSG, offending: offender.method_name)
52
+ add_offense(node, message: msg) do |corrector|
53
+ autocorrect(corrector, node, offender)
44
54
  end
45
55
  end
46
56
  end
47
57
 
48
- private
58
+ def offending_node(node)
59
+ parent(node).each_child_node.find do |sibling|
60
+ break if sibling.equal?(node)
49
61
 
50
- def in_spec_block?(node)
51
- node.each_ancestor(:block).any? do |ancestor|
52
- Examples::ALL.include?(ancestor.method_name)
62
+ yield sibling if offending?(sibling)
53
63
  end
54
64
  end
65
+
66
+ def parent(node)
67
+ node.each_ancestor(:block).first.body
68
+ end
69
+
70
+ def autocorrect(corrector, node, sibling)
71
+ RuboCop::RSpec::Corrector::MoveNode.new(
72
+ node, corrector, processed_source
73
+ ).move_before(sibling)
74
+ end
75
+
76
+ def offending?(node)
77
+ let?(node) ||
78
+ hook?(node) ||
79
+ example?(node) ||
80
+ spec_group?(node) ||
81
+ include?(node)
82
+ end
55
83
  end
56
84
  end
57
85
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that no class, module, or constant is declared.
7
+ #
8
+ # Constants, including classes and modules, when declared in a block
9
+ # scope, are defined in global namespace, and leak between examples.
10
+ #
11
+ # If several examples may define a `DummyClass`, instead of being a
12
+ # blank slate class as it will be in the first example, subsequent
13
+ # examples will be reopening it and modifying its behavior in
14
+ # unpredictable ways.
15
+ # Even worse when a class that exists in the codebase is reopened.
16
+ #
17
+ # Anonymous classes are fine, since they don't result in global
18
+ # namespace name clashes.
19
+ #
20
+ # @see https://rspec.info/features/3-12/rspec-mocks/mutating-constants
21
+ #
22
+ # @example Constants leak between examples
23
+ # # bad
24
+ # describe SomeClass do
25
+ # OtherClass = Struct.new
26
+ # CONSTANT_HERE = 'I leak into global namespace'
27
+ # end
28
+ #
29
+ # # good
30
+ # describe SomeClass do
31
+ # before do
32
+ # stub_const('OtherClass', Struct.new)
33
+ # stub_const('CONSTANT_HERE', 'I only exist during this example')
34
+ # end
35
+ # end
36
+ #
37
+ # @example
38
+ # # bad
39
+ # describe SomeClass do
40
+ # class FooClass < described_class
41
+ # def double_that
42
+ # some_base_method * 2
43
+ # end
44
+ # end
45
+ #
46
+ # it { expect(FooClass.new.double_that).to eq(4) }
47
+ # end
48
+ #
49
+ # # good - anonymous class, no constant needs to be defined
50
+ # describe SomeClass do
51
+ # let(:foo_class) do
52
+ # Class.new(described_class) do
53
+ # def double_that
54
+ # some_base_method * 2
55
+ # end
56
+ # end
57
+ # end
58
+ #
59
+ # it { expect(foo_class.new.double_that).to eq(4) }
60
+ # end
61
+ #
62
+ # # good - constant is stubbed
63
+ # describe SomeClass do
64
+ # before do
65
+ # foo_class = Class.new(described_class) do
66
+ # def do_something
67
+ # end
68
+ # end
69
+ # stub_const('FooClass', foo_class)
70
+ # end
71
+ #
72
+ # it { expect(FooClass.new.double_that).to eq(4) }
73
+ # end
74
+ #
75
+ # @example
76
+ # # bad
77
+ # describe SomeClass do
78
+ # module SomeModule
79
+ # class SomeClass
80
+ # def do_something
81
+ # end
82
+ # end
83
+ # end
84
+ # end
85
+ #
86
+ # # good
87
+ # describe SomeClass do
88
+ # before do
89
+ # foo_class = Class.new(described_class) do
90
+ # def do_something
91
+ # end
92
+ # end
93
+ # stub_const('SomeModule::SomeClass', foo_class)
94
+ # end
95
+ # end
96
+ class LeakyConstantDeclaration < Base
97
+ MSG_CONST = 'Stub constant instead of declaring explicitly.'
98
+ MSG_CLASS = 'Stub class constant instead of declaring explicitly.'
99
+ MSG_MODULE = 'Stub module constant instead of declaring explicitly.'
100
+
101
+ def on_casgn(node)
102
+ return unless inside_describe_block?(node)
103
+
104
+ add_offense(node, message: MSG_CONST)
105
+ end
106
+
107
+ def on_class(node)
108
+ return unless inside_describe_block?(node)
109
+
110
+ add_offense(node, message: MSG_CLASS)
111
+ end
112
+
113
+ def on_module(node)
114
+ return unless inside_describe_block?(node)
115
+
116
+ add_offense(node, message: MSG_MODULE)
117
+ end
118
+
119
+ private
120
+
121
+ def inside_describe_block?(node)
122
+ node.each_ancestor(:block).any? { |ancestor| spec_group?(ancestor) }
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for `let` definitions that come after an example.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # let(:foo) { bar }
11
+ #
12
+ # it 'checks what foo does' do
13
+ # expect(foo).to be
14
+ # end
15
+ #
16
+ # let(:some) { other }
17
+ #
18
+ # it 'checks what some does' do
19
+ # expect(some).to be
20
+ # end
21
+ #
22
+ # # good
23
+ # let(:foo) { bar }
24
+ # let(:some) { other }
25
+ #
26
+ # it 'checks what foo does' do
27
+ # expect(foo).to be
28
+ # end
29
+ #
30
+ # it 'checks what some does' do
31
+ # expect(some).to be
32
+ # end
33
+ class LetBeforeExamples < Base
34
+ extend AutoCorrector
35
+
36
+ MSG = 'Move `let` before the examples in the group.'
37
+
38
+ # @!method example_or_group?(node)
39
+ def_node_matcher :example_or_group?, <<~PATTERN
40
+ {
41
+ (block (send nil? {#ExampleGroups.all #Examples.all} ...) ...)
42
+ (send nil? #Includes.examples ...)
43
+ }
44
+ PATTERN
45
+
46
+ # @!method include_examples?(node)
47
+ def_node_matcher :include_examples?, <<~PATTERN
48
+ {
49
+ (block (send nil? :include_examples ...) ...)
50
+ (send nil? :include_examples ...)
51
+ }
52
+ PATTERN
53
+
54
+ def self.autocorrect_incompatible_with
55
+ [RSpec::ScatteredLet]
56
+ end
57
+
58
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
59
+ return unless example_group_with_body?(node)
60
+
61
+ check_let_declarations(node.body) if multiline_block?(node.body)
62
+ end
63
+
64
+ private
65
+
66
+ def example_group_with_include_examples?(body)
67
+ body.children.any? { |sibling| include_examples?(sibling) }
68
+ end
69
+
70
+ def multiline_block?(block)
71
+ block.begin_type?
72
+ end
73
+
74
+ def check_let_declarations(node)
75
+ first_example = find_first_example(node)
76
+ return unless first_example
77
+
78
+ correct = !example_group_with_include_examples?(node)
79
+
80
+ first_example.right_siblings.each do |sibling|
81
+ next unless let?(sibling)
82
+
83
+ add_offense(sibling) do |corrector|
84
+ autocorrect(corrector, sibling, first_example) if correct
85
+ end
86
+ end
87
+ end
88
+
89
+ def find_first_example(node)
90
+ node.children.find { |sibling| example_or_group?(sibling) }
91
+ end
92
+
93
+ def autocorrect(corrector, node, first_example)
94
+ RuboCop::RSpec::Corrector::MoveNode.new(
95
+ node, corrector, processed_source
96
+ ).move_before(first_example)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -6,50 +6,66 @@ module RuboCop
6
6
  # Checks unreferenced `let!` calls being used for test setup.
7
7
  #
8
8
  # @example
9
- # # Bad
9
+ # # bad
10
10
  # let!(:my_widget) { create(:widget) }
11
11
  #
12
12
  # it 'counts widgets' do
13
13
  # expect(Widget.count).to eq(1)
14
14
  # end
15
15
  #
16
- # # Good
16
+ # # good
17
17
  # it 'counts widgets' do
18
18
  # create(:widget)
19
19
  # expect(Widget.count).to eq(1)
20
20
  # end
21
21
  #
22
- # # Good
22
+ # # good
23
23
  # before { create(:widget) }
24
24
  #
25
25
  # it 'counts widgets' do
26
26
  # expect(Widget.count).to eq(1)
27
27
  # end
28
- class LetSetup < Cop
29
- include RuboCop::RSpec::SpecOnly,
30
- RuboCop::RSpec::TopLevelDescribe,
31
- RuboCop::RSpec::Language,
32
- RuboCop::RSpec::Language::NodePattern
28
+ class LetSetup < Base
29
+ MSG = 'Do not use `let!` to setup objects not referenced in tests.'
33
30
 
34
- MSG = 'Do not use `let!` for test setup.'.freeze
31
+ # @!method example_or_shared_group_or_including?(node)
32
+ def_node_matcher :example_or_shared_group_or_including?, <<~PATTERN
33
+ (block {
34
+ (send #rspec? {#SharedGroups.all #ExampleGroups.all} ...)
35
+ (send nil? #Includes.all ...)
36
+ } ...)
37
+ PATTERN
35
38
 
36
- def_node_search :let_bang, '(block $(send nil :let! (sym $_)) args ...)'
39
+ # @!method let_bang(node)
40
+ def_node_matcher :let_bang, <<~PATTERN
41
+ {
42
+ (block $(send nil? :let! {(sym $_) (str $_)}) ...)
43
+ $(send nil? :let! {(sym $_) (str $_)} block_pass)
44
+ }
45
+ PATTERN
37
46
 
38
- def_node_search :method_called?, '(send nil %)'
47
+ # @!method method_called?(node)
48
+ def_node_search :method_called?, '(send nil? %)'
39
49
 
40
- def on_block(node)
41
- return unless example_group?(node)
50
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
51
+ return unless example_or_shared_group_or_including?(node)
42
52
 
43
53
  unused_let_bang(node) do |let|
44
- add_offense(let, :expression)
54
+ add_offense(let)
45
55
  end
46
56
  end
47
57
 
48
58
  private
49
59
 
50
60
  def unused_let_bang(node)
51
- let_bang(node) do |method_send, method_name|
52
- yield(method_send) unless method_called?(node, method_name)
61
+ child_let_bang(node) do |method_send, method_name|
62
+ yield(method_send) unless method_called?(node, method_name.to_sym)
63
+ end
64
+ end
65
+
66
+ def child_let_bang(node, &block)
67
+ RuboCop::RSpec::ExampleGroup.new(node).lets.each do |let|
68
+ let_bang(let, &block)
53
69
  end
54
70
  end
55
71
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks where `match_array` is used.
7
+ #
8
+ # This cop checks for the following:
9
+ # - Prefer `contain_exactly` when matching an array with values.
10
+ # - Prefer `eq` when using `match_array` with an empty array literal.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # it { is_expected.to match_array([content1, content2]) }
15
+ #
16
+ # # good
17
+ # it { is_expected.to contain_exactly(content1, content2) }
18
+ #
19
+ # # good
20
+ # it { is_expected.to match_array([content] + array) }
21
+ #
22
+ # # good
23
+ # it { is_expected.to match_array(%w(tremble in fear foolish mortals)) }
24
+ #
25
+ class MatchArray < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Prefer `contain_exactly` when matching an array literal.'
29
+ RESTRICT_ON_SEND = %i[match_array].freeze
30
+
31
+ # @!method match_array_with_empty_array?(node)
32
+ def_node_matcher :match_array_with_empty_array?, <<~PATTERN
33
+ (send nil? :match_array (array))
34
+ PATTERN
35
+
36
+ def on_send(node)
37
+ return unless node.first_argument&.array_type?
38
+ return if match_array_with_empty_array?(node)
39
+
40
+ check_populated_array(node)
41
+ end
42
+
43
+ private
44
+
45
+ def check_populated_array(node)
46
+ return if node.first_argument.percent_literal?
47
+
48
+ add_offense(node) do |corrector|
49
+ array_contents = node.arguments.flat_map(&:to_a)
50
+ corrector.replace(
51
+ node,
52
+ "contain_exactly(#{array_contents.map(&:source).join(', ')})"
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RuboCop
2
4
  module Cop
3
5
  module RSpec
@@ -7,25 +9,18 @@ module RuboCop
7
9
  # # bad
8
10
  # allow(foo).to receive_message_chain(:bar, :baz).and_return(42)
9
11
  #
10
- # # better
12
+ # # good
11
13
  # thing = Thing.new(baz: 42)
12
- # allow(foo).to receive(bar: thing)
14
+ # allow(foo).to receive(:bar).and_return(thing)
13
15
  #
14
- class MessageChain < Cop
15
- include RuboCop::RSpec::SpecOnly
16
-
17
- MESSAGE = 'Avoid stubbing using `%<method>s`'.freeze
18
-
19
- MESSAGE_CHAIN_METHODS = [
20
- :receive_message_chain,
21
- :stub_chain
22
- ].freeze
16
+ class MessageChain < Base
17
+ MSG = 'Avoid stubbing using `%<method>s`.'
18
+ RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
23
19
 
24
20
  def on_send(node)
25
- _receiver, method_name, *_args = *node
26
- return unless MESSAGE_CHAIN_METHODS.include?(method_name)
27
-
28
- add_offense(node, :selector, MESSAGE % { method: method_name })
21
+ add_offense(
22
+ node.loc.selector, message: format(MSG, method: node.method_name)
23
+ )
29
24
  end
30
25
  end
31
26
  end
@@ -8,7 +8,7 @@ module RuboCop
8
8
  # This cop can be configured in your configuration using the
9
9
  # `EnforcedStyle` option and supports `--auto-gen-config`.
10
10
  #
11
- # @example `EnforcedStyle: allow`
11
+ # @example `EnforcedStyle: allow` (default)
12
12
  #
13
13
  # # bad
14
14
  # expect(foo).to receive(:bar)
@@ -24,24 +24,27 @@ module RuboCop
24
24
  # # good
25
25
  # expect(foo).to receive(:bar)
26
26
  #
27
- class MessageExpectation < Cop
28
- include RuboCop::RSpec::SpecOnly, ConfigurableEnforcedStyle
27
+ class MessageExpectation < Base
28
+ include ConfigurableEnforcedStyle
29
29
 
30
- MSG = 'Prefer `%s` for setting message expectations.'.freeze
30
+ MSG = 'Prefer `%<style>s` for setting message expectations.'
31
31
 
32
- SUPPORTED_STYLES = %w(allow expect).freeze
32
+ RESTRICT_ON_SEND = %i[to].freeze
33
33
 
34
- def_node_matcher :message_expectation, <<-PATTERN
35
- (send $(send nil {:expect :allow} ...) :to #receive_message?)
34
+ # @!method message_expectation(node)
35
+ def_node_matcher :message_expectation, <<~PATTERN
36
+ (send $(send nil? {:expect :allow} ...) :to #receive_message?)
36
37
  PATTERN
37
38
 
38
- def_node_search :receive_message?, '(send nil :receive ...)'
39
+ # @!method receive_message?(node)
40
+ def_node_search :receive_message?, '(send nil? :receive ...)'
39
41
 
40
42
  def on_send(node)
41
43
  message_expectation(node) do |match|
42
44
  return correct_style_detected if preferred_style?(match)
43
45
 
44
- add_offense(match, :selector, MSG % style) do
46
+ message = format(MSG, style: style)
47
+ add_offense(match.loc.selector, message: message) do
45
48
  opposite_style_detected
46
49
  end
47
50
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that message expectations are set using spies.
7
+ #
8
+ # This cop can be configured in your configuration using the
9
+ # `EnforcedStyle` option and supports `--auto-gen-config`.
10
+ #
11
+ # @example `EnforcedStyle: have_received` (default)
12
+ #
13
+ # # bad
14
+ # expect(foo).to receive(:bar)
15
+ # do_something
16
+ #
17
+ # # good
18
+ # allow(foo).to receive(:bar) # or use instance_spy
19
+ # do_something
20
+ # expect(foo).to have_received(:bar)
21
+ #
22
+ # @example `EnforcedStyle: receive`
23
+ #
24
+ # # bad
25
+ # allow(foo).to receive(:bar)
26
+ # do_something
27
+ # expect(foo).to have_received(:bar)
28
+ #
29
+ # # good
30
+ # expect(foo).to receive(:bar)
31
+ # do_something
32
+ #
33
+ class MessageSpies < Base
34
+ include ConfigurableEnforcedStyle
35
+
36
+ MSG_RECEIVE = 'Prefer `receive` for setting message expectations.'
37
+
38
+ MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
39
+ 'expectations. Setup `%<source>s` as a spy using ' \
40
+ '`allow` or `instance_spy`.'
41
+
42
+ RESTRICT_ON_SEND = Runners.all
43
+
44
+ # @!method message_expectation(node)
45
+ def_node_matcher :message_expectation, %(
46
+ (send (send nil? :expect $_) #Runners.all ...)
47
+ )
48
+
49
+ # @!method receive_message(node)
50
+ def_node_search :receive_message, %(
51
+ $(send nil? {:receive :have_received} ...)
52
+ )
53
+
54
+ def on_send(node)
55
+ receive_message_matcher(node) do |receiver, message_matcher|
56
+ return correct_style_detected if preferred_style?(message_matcher)
57
+
58
+ add_offense(
59
+ message_matcher.loc.selector,
60
+ message: error_message(receiver)
61
+ ) { opposite_style_detected }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def receive_message_matcher(node)
68
+ return unless (receiver = message_expectation(node))
69
+
70
+ receive_message(node) { |match| yield(receiver, match) }
71
+ end
72
+
73
+ def preferred_style?(expectation)
74
+ expectation.method_name.equal?(style)
75
+ end
76
+
77
+ def error_message(receiver)
78
+ case style
79
+ when :receive
80
+ MSG_RECEIVE
81
+ when :have_received
82
+ format(MSG_HAVE_RECEIVED, source: receiver.source)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end