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,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks for multiple top level describes.
6
+ # Checks for multiple top-level example groups.
7
7
  #
8
8
  # Multiple descriptions for the same class or module should either
9
9
  # be nested or separated into different test files.
@@ -15,25 +15,27 @@ module RuboCop
15
15
  # describe MyClass, '.do_something_else' do
16
16
  # end
17
17
  #
18
- # #good
19
- # describe MyClass
18
+ # # good
19
+ # describe MyClass do
20
20
  # describe '.do_something' do
21
21
  # end
22
22
  # describe '.do_something_else' do
23
23
  # end
24
24
  # end
25
- class MultipleDescribes < Cop
26
- include RuboCop::RSpec::SpecOnly,
27
- RuboCop::RSpec::TopLevelDescribe
25
+ #
26
+ class MultipleDescribes < Base
27
+ include TopLevelGroup
28
+
29
+ MSG = 'Do not use multiple top-level example groups - try to nest them.'
28
30
 
29
- MSG = 'Do not use multiple top level describes - ' \
30
- 'try to nest them.'.freeze
31
+ def on_top_level_group(node)
32
+ top_level_example_groups =
33
+ top_level_groups.select { |group| example_group?(group) }
31
34
 
32
- def on_top_level_describe(node, _args)
33
- return if single_top_level_describe?
34
- return unless top_level_nodes.first.equal?(node)
35
+ return if top_level_example_groups.one?
36
+ return unless top_level_example_groups.first.equal?(node)
35
37
 
36
- add_offense(node, :expression)
38
+ add_offense(node.send_node)
37
39
  end
38
40
  end
39
41
  end
@@ -11,7 +11,6 @@ module RuboCop
11
11
  # and works with `--auto-gen-config`.
12
12
  #
13
13
  # @example
14
- #
15
14
  # # bad
16
15
  # describe UserCreator do
17
16
  # it 'builds a user' do
@@ -26,18 +25,31 @@ module RuboCop
26
25
  # expect(user.name).to eq("John")
27
26
  # end
28
27
  #
29
- # it 'sets the users age'
28
+ # it 'sets the users age' do
30
29
  # expect(user.age).to eq(22)
31
30
  # end
32
31
  # end
33
32
  #
34
- # @example configuration
33
+ # @example `aggregate_failures: true` (default)
34
+ # # good - the cop ignores when RSpec aggregates failures
35
+ # describe UserCreator do
36
+ # it 'builds a user', :aggregate_failures do
37
+ # expect(user.name).to eq("John")
38
+ # expect(user.age).to eq(22)
39
+ # end
40
+ # end
35
41
  #
36
- # # .rubocop.yml
37
- # RSpec/MultipleExpectations:
38
- # Max: 2
42
+ # @example `aggregate_failures: false`
43
+ # # Detected as an offense
44
+ # describe UserCreator do
45
+ # it 'builds a user', aggregate_failures: false do
46
+ # expect(user.name).to eq("John")
47
+ # expect(user.age).to eq(22)
48
+ # end
49
+ # end
39
50
  #
40
- # # not flagged by rubocop
51
+ # @example `Max: 1` (default)
52
+ # # bad
41
53
  # describe UserCreator do
42
54
  # it 'builds a user' do
43
55
  # expect(user.name).to eq("John")
@@ -45,43 +57,91 @@ module RuboCop
45
57
  # end
46
58
  # end
47
59
  #
48
- class MultipleExpectations < Cop
49
- include RuboCop::RSpec::SpecOnly,
50
- RuboCop::RSpec::Language,
51
- ConfigurableMax
60
+ # @example `Max: 2`
61
+ # # good
62
+ # describe UserCreator do
63
+ # it 'builds a user' do
64
+ # expect(user.name).to eq("John")
65
+ # expect(user.age).to eq(22)
66
+ # end
67
+ # end
68
+ #
69
+ class MultipleExpectations < Base
70
+ include ConfigurableMax
71
+
72
+ MSG = 'Example has too many expectations [%<total>d/%<max>d].'
52
73
 
53
- MSG = 'Too many expectations.'.freeze
74
+ ANYTHING = ->(_node) { true }
75
+ TRUE_NODE = lambda(&:true_type?)
54
76
 
55
- def_node_matcher :example?, <<-PATTERN
56
- (block (send _ {#{Examples::ALL.to_node_pattern}} ...) ...)
77
+ # @!method aggregate_failures?(node)
78
+ def_node_matcher :aggregate_failures?, <<~PATTERN
79
+ (block {
80
+ (send _ _ <(sym :aggregate_failures) ...>)
81
+ (send _ _ ... (hash <(pair (sym :aggregate_failures) %1) ...>))
82
+ } ...)
57
83
  PATTERN
58
84
 
59
- def_node_search :expect, '(send _ :expect ...)'
85
+ # @!method expect?(node)
86
+ def_node_matcher :expect?, '(send nil? #Expectations.all ...)'
87
+
88
+ # @!method aggregate_failures_block?(node)
89
+ def_node_matcher :aggregate_failures_block?, <<~PATTERN
90
+ (block (send nil? :aggregate_failures ...) ...)
91
+ PATTERN
60
92
 
61
- def on_block(node)
62
- return unless example?(node) && (expectations = expect(node))
93
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
94
+ return unless example?(node)
63
95
 
64
- return if expectations.count <= max_expectations
96
+ return if example_with_aggregate_failures?(node)
65
97
 
66
- self.max = expectations.count
98
+ expectations_count = to_enum(:find_expectation, node).count
67
99
 
68
- flag_example(node, expectation_count: expectations.count)
100
+ return if expectations_count <= max_expectations
101
+
102
+ self.max = expectations_count
103
+
104
+ flag_example(node, expectation_count: expectations_count)
69
105
  end
70
106
 
71
107
  private
72
108
 
73
- def flag_example(node, expectation_count:)
74
- method, = *node
109
+ def example_with_aggregate_failures?(example_node)
110
+ node_with_aggregate_failures = find_aggregate_failures(example_node)
111
+ return false unless node_with_aggregate_failures
75
112
 
113
+ aggregate_failures?(node_with_aggregate_failures, TRUE_NODE)
114
+ end
115
+
116
+ def find_aggregate_failures(example_node)
117
+ example_node.send_node.each_ancestor(:block)
118
+ .find { |block_node| aggregate_failures?(block_node, ANYTHING) }
119
+ end
120
+
121
+ def find_expectation(node, &block)
122
+ yield if expect?(node) || aggregate_failures_block?(node)
123
+
124
+ # do not search inside of aggregate_failures block
125
+ return if aggregate_failures_block?(node)
126
+
127
+ node.each_child_node do |child|
128
+ find_expectation(child, &block)
129
+ end
130
+ end
131
+
132
+ def flag_example(node, expectation_count:)
76
133
  add_offense(
77
- method,
78
- :expression,
79
- MSG % { total: expectation_count, max: max_expectations }
134
+ node.send_node,
135
+ message: format(
136
+ MSG,
137
+ total: expectation_count,
138
+ max: max_expectations
139
+ )
80
140
  )
81
141
  end
82
142
 
83
143
  def max_expectations
84
- Integer(cop_config.fetch(parameter_name, 1))
144
+ Integer(cop_config.fetch('Max', 1))
85
145
  end
86
146
  end
87
147
  end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks if example groups contain too many `let` and `subject` calls.
7
+ #
8
+ # This cop is configurable using the `Max` option and the `AllowSubject`
9
+ # which will configure the cop to only register offenses on calls to
10
+ # `let` and not calls to `subject`.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # describe MyClass do
15
+ # let(:foo) { [] }
16
+ # let(:bar) { [] }
17
+ # let!(:baz) { [] }
18
+ # let(:qux) { [] }
19
+ # let(:quux) { [] }
20
+ # let(:quuz) { {} }
21
+ # end
22
+ #
23
+ # describe MyClass do
24
+ # let(:foo) { [] }
25
+ # let(:bar) { [] }
26
+ # let!(:baz) { [] }
27
+ #
28
+ # context 'when stuff' do
29
+ # let(:qux) { [] }
30
+ # let(:quux) { [] }
31
+ # let(:quuz) { {} }
32
+ # end
33
+ # end
34
+ #
35
+ # # good
36
+ # describe MyClass do
37
+ # let(:bar) { [] }
38
+ # let!(:baz) { [] }
39
+ # let(:qux) { [] }
40
+ # let(:quux) { [] }
41
+ # let(:quuz) { {} }
42
+ # end
43
+ #
44
+ # describe MyClass do
45
+ # context 'when stuff' do
46
+ # let(:foo) { [] }
47
+ # let(:bar) { [] }
48
+ # let!(:booger) { [] }
49
+ # end
50
+ #
51
+ # context 'when other stuff' do
52
+ # let(:qux) { [] }
53
+ # let(:quux) { [] }
54
+ # let(:quuz) { {} }
55
+ # end
56
+ # end
57
+ #
58
+ # @example when disabling AllowSubject configuration
59
+ # # rubocop.yml
60
+ # # RSpec/MultipleMemoizedHelpers:
61
+ # # AllowSubject: false
62
+ #
63
+ # # bad - `subject` counts towards memoized helpers
64
+ # describe MyClass do
65
+ # subject { {} }
66
+ # let(:foo) { [] }
67
+ # let(:bar) { [] }
68
+ # let!(:baz) { [] }
69
+ # let(:qux) { [] }
70
+ # let(:quux) { [] }
71
+ # end
72
+ #
73
+ # @example with Max configuration
74
+ # # rubocop.yml
75
+ # # RSpec/MultipleMemoizedHelpers:
76
+ # # Max: 1
77
+ #
78
+ # # bad
79
+ # describe MyClass do
80
+ # let(:foo) { [] }
81
+ # let(:bar) { [] }
82
+ # end
83
+ #
84
+ class MultipleMemoizedHelpers < Base
85
+ include ConfigurableMax
86
+ include Variable
87
+
88
+ MSG = 'Example group has too many memoized helpers [%<count>d/%<max>d]'
89
+
90
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
91
+ return unless spec_group?(node)
92
+
93
+ count = all_helpers(node).uniq.count
94
+
95
+ return if count <= max
96
+
97
+ self.max = count
98
+ add_offense(node, message: format(MSG, count: count, max: max))
99
+ end
100
+
101
+ def on_new_investigation
102
+ super
103
+ @example_group_memoized_helpers = {}
104
+ end
105
+
106
+ private
107
+
108
+ attr_reader :example_group_memoized_helpers
109
+
110
+ def all_helpers(node)
111
+ helpers(node) +
112
+ node.each_ancestor(:block).flat_map { |ancestor| helpers(ancestor) }
113
+ end
114
+
115
+ def helpers(node)
116
+ @example_group_memoized_helpers[node] ||=
117
+ variable_nodes(node).map do |variable_node|
118
+ if variable_node.block_type?
119
+ variable_definition?(variable_node.send_node)
120
+ else # block-pass (`let(:foo, &bar)`)
121
+ variable_definition?(variable_node)
122
+ end
123
+ end
124
+ end
125
+
126
+ def variable_nodes(node)
127
+ example_group = RuboCop::RSpec::ExampleGroup.new(node)
128
+
129
+ if allow_subject?
130
+ example_group.lets
131
+ else
132
+ example_group.lets + example_group.subjects
133
+ end
134
+ end
135
+
136
+ def max
137
+ cop_config['Max']
138
+ end
139
+
140
+ def allow_subject?
141
+ cop_config['AllowSubject']
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks if an example group defines `subject` multiple times.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe Foo do
11
+ # subject(:user) { User.new }
12
+ # subject(:post) { Post.new }
13
+ # end
14
+ #
15
+ # # good
16
+ # describe Foo do
17
+ # let(:user) { User.new }
18
+ # subject(:post) { Post.new }
19
+ # end
20
+ #
21
+ # # bad (does not support autocorrection)
22
+ # describe Foo do
23
+ # subject!(:user) { User.new }
24
+ # subject!(:post) { Post.new }
25
+ # end
26
+ #
27
+ # # good
28
+ # describe Foo do
29
+ # before do
30
+ # User.new
31
+ # Post.new
32
+ # end
33
+ # end
34
+ #
35
+ # This cop does not support autocorrection in some cases.
36
+ # The autocorrect behavior for this cop depends on the type of
37
+ # duplication:
38
+ #
39
+ # - If multiple named subjects are defined then this probably indicates
40
+ # that the overwritten subjects (all subjects except the last
41
+ # definition) are effectively being used to define helpers. In this
42
+ # case they are replaced with `let`.
43
+ #
44
+ # - If multiple unnamed subjects are defined though then this can *only*
45
+ # be dead code and we remove the overwritten subject definitions.
46
+ #
47
+ # - If subjects are defined with `subject!` then we don't autocorrect.
48
+ # This is enough of an edge case that people can just move this to
49
+ # a `before` hook on their own
50
+ #
51
+ class MultipleSubjects < Base
52
+ extend AutoCorrector
53
+ include RangeHelp
54
+
55
+ MSG = 'Do not set more than one subject per example group'
56
+
57
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
58
+ return unless example_group?(node)
59
+
60
+ subjects = RuboCop::RSpec::ExampleGroup.new(node).subjects
61
+
62
+ subjects[0...-1].each do |subject|
63
+ add_offense(subject) do |corrector|
64
+ autocorrect(corrector, subject)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def autocorrect(corrector, subject)
72
+ return unless subject.method_name.equal?(:subject) # Ignore `subject!`
73
+
74
+ if named_subject?(subject)
75
+ rename_autocorrect(corrector, subject)
76
+ else
77
+ remove_autocorrect(corrector, subject)
78
+ end
79
+ end
80
+
81
+ def named_subject?(node)
82
+ node.send_node.arguments?
83
+ end
84
+
85
+ def rename_autocorrect(corrector, node)
86
+ corrector.replace(node.send_node.loc.selector, 'let')
87
+ end
88
+
89
+ def remove_autocorrect(corrector, node)
90
+ range = range_by_whole_lines(node.source_range,
91
+ include_final_newline: true)
92
+ corrector.remove(range)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -6,13 +6,17 @@ module RuboCop
6
6
  # Checks for explicitly referenced test subjects.
7
7
  #
8
8
  # RSpec lets you declare an "implicit subject" using `subject { ... }`
9
- # which allows for tests like `it { should be_valid }`. If you need to
10
- # reference your test subject you should explicitly name it using
11
- # `subject(:your_subject_name) { ... }`. Your test subjects should be
12
- # the most important object in your tests so they deserve a descriptive
13
- # name.
9
+ # which allows for tests like `it { is_expected.to be_valid }`.
10
+ # If you need to reference your test subject you should explicitly
11
+ # name it using `subject(:your_subject_name) { ... }`. Your test subjects
12
+ # should be the most important object in your tests so they deserve
13
+ # a descriptive name.
14
14
  #
15
- # @example
15
+ # This cop can be configured in your configuration using `EnforcedStyle`,
16
+ # and `IgnoreSharedExamples` which will not report offenses for implicit
17
+ # subjects in shared example groups.
18
+ #
19
+ # @example `EnforcedStyle: always` (default)
16
20
  # # bad
17
21
  # RSpec.describe User do
18
22
  # subject { described_class.new }
@@ -23,7 +27,7 @@ module RuboCop
23
27
  # end
24
28
  #
25
29
  # # good
26
- # RSpec.describe Foo do
30
+ # RSpec.describe User do
27
31
  # subject(:user) { described_class.new }
28
32
  #
29
33
  # it 'is valid' do
@@ -32,44 +36,120 @@ module RuboCop
32
36
  # end
33
37
  #
34
38
  # # also good
35
- # RSpec.describe Foo do
39
+ # RSpec.describe User do
40
+ # subject(:user) { described_class.new }
41
+ #
42
+ # it { is_expected.to be_valid }
43
+ # end
44
+ #
45
+ # @example `EnforcedStyle: named_only`
46
+ # # bad
47
+ # RSpec.describe User do
48
+ # subject(:user) { described_class.new }
49
+ #
50
+ # it 'is valid' do
51
+ # expect(subject.valid?).to be(true)
52
+ # end
53
+ # end
54
+ #
55
+ # # good
56
+ # RSpec.describe User do
36
57
  # subject(:user) { described_class.new }
37
58
  #
38
- # it { should be_valid }
59
+ # it 'is valid' do
60
+ # expect(user.valid?).to be(true)
61
+ # end
62
+ # end
63
+ #
64
+ # # also good
65
+ # RSpec.describe User do
66
+ # subject { described_class.new }
67
+ #
68
+ # it { is_expected.to be_valid }
39
69
  # end
40
- class NamedSubject < Cop
41
- include RuboCop::RSpec::SpecOnly
70
+ #
71
+ # # acceptable
72
+ # RSpec.describe User do
73
+ # subject { described_class.new }
74
+ #
75
+ # it 'is valid' do
76
+ # expect(subject.valid?).to be(true)
77
+ # end
78
+ # end
79
+ class NamedSubject < Base
80
+ include ConfigurableEnforcedStyle
42
81
 
43
- MSG = 'Name your test subject if '\
44
- 'you need to reference it explicitly.'.freeze
82
+ MSG = 'Name your test subject if you need to reference it explicitly.'
45
83
 
46
- def_node_matcher :rspec_block?, <<-PATTERN
47
- (block
48
- (send nil {:it :specify :before :after :around} ...)
49
- ...)
84
+ # @!method example_or_hook_block?(node)
85
+ def_node_matcher :example_or_hook_block?, <<~PATTERN
86
+ (block (send nil? {#Examples.all #Hooks.all} ...) ...)
50
87
  PATTERN
51
88
 
52
- def_node_matcher :unnamed_subject, '$(send nil :subject)'
89
+ # @!method shared_example?(node)
90
+ def_node_matcher :shared_example?, <<~PATTERN
91
+ (block (send #rspec? #SharedGroups.examples ...) ...)
92
+ PATTERN
93
+
94
+ # @!method subject_usage(node)
95
+ def_node_search :subject_usage, '$(send nil? :subject)'
53
96
 
54
- def on_block(node)
55
- return unless rspec_block?(node)
97
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
98
+ if !example_or_hook_block?(node) || ignored_shared_example?(node)
99
+ return
100
+ end
56
101
 
57
102
  subject_usage(node) do |subject_node|
58
- add_offense(subject_node, :selector)
103
+ check_explicit_subject(subject_node)
59
104
  end
60
105
  end
61
106
 
62
107
  private
63
108
 
64
- def subject_usage(node, &block)
65
- return unless node.instance_of?(Node)
66
-
67
- unnamed_subject(node, &block)
109
+ def ignored_shared_example?(node)
110
+ return false unless cop_config['IgnoreSharedExamples']
68
111
 
69
- node.children.each do |child|
70
- subject_usage(child, &block)
112
+ node.each_ancestor(:block).any? do |ancestor|
113
+ shared_example?(ancestor)
71
114
  end
72
115
  end
116
+
117
+ def check_explicit_subject(node)
118
+ return if allow_explicit_subject?(node)
119
+
120
+ add_offense(node.loc.selector)
121
+ end
122
+
123
+ def allow_explicit_subject?(node)
124
+ !always? && !named_only?(node)
125
+ end
126
+
127
+ def always?
128
+ style == :always
129
+ end
130
+
131
+ def named_only?(node)
132
+ style == :named_only &&
133
+ subject_definition_is_named?(node)
134
+ end
135
+
136
+ def subject_definition_is_named?(node)
137
+ subject = nearest_subject(node)
138
+
139
+ subject&.send_node&.arguments?
140
+ end
141
+
142
+ def nearest_subject(node)
143
+ node
144
+ .each_ancestor(:block)
145
+ .lazy
146
+ .map { |block_node| find_subject(block_node) }
147
+ .find(&:itself)
148
+ end
149
+
150
+ def find_subject(block_node)
151
+ block_node.body&.child_nodes&.find { |send_node| subject?(send_node) }
152
+ end
73
153
  end
74
154
  end
75
155
  end