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,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for redundant predicate matcher.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # expect(foo).to be_exist(bar)
11
+ # expect(foo).not_to be_include(bar)
12
+ # expect(foo).to be_all(bar)
13
+ #
14
+ # # good
15
+ # expect(foo).to exist(bar)
16
+ # expect(foo).not_to include(bar)
17
+ # expect(foo).to all be(bar)
18
+ #
19
+ class RedundantPredicateMatcher < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'
23
+ RESTRICT_ON_SEND =
24
+ %i[be_all be_cover be_end_with be_eql be_equal
25
+ be_exist be_exists be_include be_match
26
+ be_respond_to be_start_with].freeze
27
+
28
+ def on_send(node)
29
+ return if node.parent.block_type? || node.arguments.empty?
30
+ return unless replaceable_arguments?(node)
31
+
32
+ method_name = node.method_name.to_s
33
+ replaced = replaced_method_name(method_name)
34
+ add_offense(node, message: message(method_name,
35
+ replaced)) do |corrector|
36
+ unless node.method?(:be_all)
37
+ corrector.replace(node.loc.selector, replaced)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def message(bad_method, good_method)
45
+ format(MSG, bad: bad_method, good: good_method)
46
+ end
47
+
48
+ def replaceable_arguments?(node)
49
+ if node.method?(:be_all)
50
+ node.first_argument.send_type?
51
+ else
52
+ true
53
+ end
54
+ end
55
+
56
+ def replaced_method_name(method_name)
57
+ name = method_name.to_s.delete_prefix('be_')
58
+ if name == 'exists'
59
+ 'exist'
60
+ else
61
+ name
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that `remove_const` is not used in specs.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it 'does something' do
11
+ # Object.send(:remove_const, :SomeConstant)
12
+ # end
13
+ #
14
+ # before do
15
+ # SomeClass.send(:remove_const, :SomeConstant)
16
+ # end
17
+ #
18
+ class RemoveConst < Base
19
+ include RuboCop::RSpec::Language
20
+
21
+ MSG = 'Do not use remove_const in specs. ' \
22
+ 'Consider using e.g. `stub_const`.'
23
+ RESTRICT_ON_SEND = %i[send __send__].freeze
24
+
25
+ # @!method remove_const(node)
26
+ def_node_matcher :remove_const, <<~PATTERN
27
+ (send _ {:send | :__send__} (sym :remove_const) _)
28
+ PATTERN
29
+
30
+ # Check for offenses
31
+ def on_send(node)
32
+ remove_const(node) do
33
+ add_offense(node)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated description strings in example groups.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # RSpec.describe User do
11
+ # it 'is valid' do
12
+ # # ...
13
+ # end
14
+ #
15
+ # it 'is valid' do
16
+ # # ...
17
+ # end
18
+ # end
19
+ #
20
+ # # good
21
+ # RSpec.describe User do
22
+ # it 'is valid when first and last name are present' do
23
+ # # ...
24
+ # end
25
+ #
26
+ # it 'is valid when last name only is present' do
27
+ # # ...
28
+ # end
29
+ # end
30
+ #
31
+ # # good
32
+ # RSpec.describe User do
33
+ # it 'is valid' do
34
+ # # ...
35
+ # end
36
+ #
37
+ # it 'is valid', :flag do
38
+ # # ...
39
+ # end
40
+ # end
41
+ #
42
+ class RepeatedDescription < Base
43
+ MSG = "Don't repeat descriptions within an example group."
44
+
45
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
46
+ return unless example_group?(node)
47
+
48
+ repeated_descriptions(node).each do |description|
49
+ add_offense(description)
50
+ end
51
+
52
+ repeated_its(node).each do |its|
53
+ add_offense(its)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # Select examples in the current scope with repeated description strings
60
+ def repeated_descriptions(node)
61
+ grouped_examples =
62
+ RuboCop::RSpec::ExampleGroup.new(node)
63
+ .examples
64
+ .reject { |n| n.definition.method?(:its) }
65
+ .group_by { |example| example_signature(example) }
66
+
67
+ grouped_examples
68
+ .select { |signatures, group| signatures.any? && group.size > 1 }
69
+ .values
70
+ .flatten
71
+ .map(&:definition)
72
+ end
73
+
74
+ def repeated_its(node)
75
+ grouped_its =
76
+ RuboCop::RSpec::ExampleGroup.new(node)
77
+ .examples
78
+ .select { |n| n.definition.method?(:its) }
79
+ .group_by { |example| its_signature(example) }
80
+
81
+ grouped_its
82
+ .select { |signatures, group| signatures.any? && group.size > 1 }
83
+ .values
84
+ .flatten
85
+ .map(&:to_node)
86
+ end
87
+
88
+ def example_signature(example)
89
+ [example.metadata, example.doc_string]
90
+ end
91
+
92
+ def its_signature(example)
93
+ [example.doc_string, example]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated examples within example groups.
7
+ #
8
+ # @example
9
+ #
10
+ # it 'is valid' do
11
+ # expect(user).to be_valid
12
+ # end
13
+ #
14
+ # it 'validates the user' do
15
+ # expect(user).to be_valid
16
+ # end
17
+ #
18
+ class RepeatedExample < Base
19
+ MSG = "Don't repeat examples within an example group."
20
+
21
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
22
+ return unless example_group?(node)
23
+
24
+ repeated_examples(node).each do |repeated_example|
25
+ add_offense(repeated_example)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def repeated_examples(node)
32
+ RuboCop::RSpec::ExampleGroup.new(node)
33
+ .examples
34
+ .group_by { |example| example_signature(example) }
35
+ .values
36
+ .reject(&:one?)
37
+ .flatten
38
+ .map(&:to_node)
39
+ end
40
+
41
+ def example_signature(example)
42
+ key_parts = [example.metadata, example.implementation]
43
+
44
+ if example.definition.method?(:its)
45
+ key_parts << example.definition.arguments
46
+ end
47
+
48
+ key_parts
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated describe and context block body.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe 'cool feature x' do
11
+ # it { cool_predicate }
12
+ # end
13
+ #
14
+ # describe 'cool feature y' do
15
+ # it { cool_predicate }
16
+ # end
17
+ #
18
+ # # good
19
+ # describe 'cool feature' do
20
+ # it { cool_predicate }
21
+ # end
22
+ #
23
+ # describe 'another cool feature' do
24
+ # it { another_predicate }
25
+ # end
26
+ #
27
+ # # good
28
+ # context 'when case x', :tag do
29
+ # it { cool_predicate }
30
+ # end
31
+ #
32
+ # context 'when case y' do
33
+ # it { cool_predicate }
34
+ # end
35
+ #
36
+ # # good
37
+ # context Array do
38
+ # it { is_expected.to respond_to :each }
39
+ # end
40
+ #
41
+ # context Hash do
42
+ # it { is_expected.to respond_to :each }
43
+ # end
44
+ #
45
+ class RepeatedExampleGroupBody < Base
46
+ include SkipOrPending
47
+
48
+ MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
49
+
50
+ # @!method several_example_groups?(node)
51
+ def_node_matcher :several_example_groups?, <<~PATTERN
52
+ (begin <#example_group_with_body? #example_group_with_body? ...>)
53
+ PATTERN
54
+
55
+ # @!method metadata(node)
56
+ def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
57
+
58
+ # @!method body(node)
59
+ def_node_matcher :body, '(block _ args $...)'
60
+
61
+ # @!method const_arg(node)
62
+ def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
63
+
64
+ def on_begin(node)
65
+ return unless several_example_groups?(node)
66
+
67
+ repeated_group_bodies(node).each do |group, repeats|
68
+ add_offense(group, message: message(group, repeats))
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def repeated_group_bodies(node)
75
+ node
76
+ .children
77
+ .select { |child| example_group_with_body?(child) }
78
+ .reject { |child| skip_or_pending_inside_block?(child) }
79
+ .group_by { |group| signature_keys(group) }
80
+ .values
81
+ .reject(&:one?)
82
+ .flat_map { |groups| add_repeated_lines(groups) }
83
+ end
84
+
85
+ def add_repeated_lines(groups)
86
+ repeated_lines = groups.map(&:first_line)
87
+ groups.map { |group| [group, repeated_lines - [group.first_line]] }
88
+ end
89
+
90
+ def signature_keys(group)
91
+ [metadata(group), body(group), const_arg(group)]
92
+ end
93
+
94
+ def message(group, repeats)
95
+ format(MSG, group: group.method_name, loc: repeats)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated example group descriptions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe 'cool feature' do
11
+ # # example group
12
+ # end
13
+ #
14
+ # describe 'cool feature' do
15
+ # # example group
16
+ # end
17
+ #
18
+ # # bad
19
+ # context 'when case x' do
20
+ # # example group
21
+ # end
22
+ #
23
+ # describe 'when case x' do
24
+ # # example group
25
+ # end
26
+ #
27
+ # # good
28
+ # describe 'cool feature' do
29
+ # # example group
30
+ # end
31
+ #
32
+ # describe 'another cool feature' do
33
+ # # example group
34
+ # end
35
+ #
36
+ # # good
37
+ # context 'when case x' do
38
+ # # example group
39
+ # end
40
+ #
41
+ # context 'when another case' do
42
+ # # example group
43
+ # end
44
+ #
45
+ class RepeatedExampleGroupDescription < Base
46
+ include SkipOrPending
47
+
48
+ MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
49
+
50
+ # @!method several_example_groups?(node)
51
+ def_node_matcher :several_example_groups?, <<~PATTERN
52
+ (begin <#example_group? #example_group? ...>)
53
+ PATTERN
54
+
55
+ # @!method doc_string_and_metadata(node)
56
+ def_node_matcher :doc_string_and_metadata, <<~PATTERN
57
+ (block (send _ _ $_ $...) ...)
58
+ PATTERN
59
+
60
+ # @!method empty_description?(node)
61
+ def_node_matcher :empty_description?, '(block (send _ _) ...)'
62
+
63
+ def on_begin(node)
64
+ return unless several_example_groups?(node)
65
+
66
+ repeated_group_descriptions(node).each do |group, repeats|
67
+ add_offense(group, message: message(group, repeats))
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def repeated_group_descriptions(node)
74
+ node
75
+ .children
76
+ .select { |child| example_group?(child) }
77
+ .reject { |child| skip_or_pending_inside_block?(child) }
78
+ .reject { |child| empty_description?(child) }
79
+ .group_by { |group| doc_string_and_metadata(group) }
80
+ .values
81
+ .reject(&:one?)
82
+ .flat_map { |groups| add_repeated_lines(groups) }
83
+ end
84
+
85
+ def add_repeated_lines(groups)
86
+ repeated_lines = groups.map(&:first_line)
87
+ groups.map { |group| [group, repeated_lines - [group.first_line]] }
88
+ end
89
+
90
+ def message(group, repeats)
91
+ format(MSG, group: group.method_name, loc: repeats)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Check for repeated include of shared examples.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe 'foo' do
11
+ # include_examples 'cool stuff'
12
+ # include_examples 'cool stuff'
13
+ # end
14
+ #
15
+ # # bad
16
+ # describe 'foo' do
17
+ # it_behaves_like 'a cool', 'thing'
18
+ # it_behaves_like 'a cool', 'thing'
19
+ # end
20
+ #
21
+ # # bad
22
+ # context 'foo' do
23
+ # it_should_behave_like 'a duck'
24
+ # it_should_behave_like 'a duck'
25
+ # end
26
+ #
27
+ # # good
28
+ # describe 'foo' do
29
+ # include_examples 'cool stuff'
30
+ # end
31
+ #
32
+ # describe 'bar' do
33
+ # include_examples 'cool stuff'
34
+ # end
35
+ #
36
+ # # good
37
+ # describe 'foo' do
38
+ # it_behaves_like 'a cool', 'thing'
39
+ # it_behaves_like 'a cool', 'person'
40
+ # end
41
+ #
42
+ # # good
43
+ # context 'foo' do
44
+ # it_should_behave_like 'a duck'
45
+ # it_should_behave_like 'a goose'
46
+ # end
47
+ #
48
+ class RepeatedIncludeExample < Base
49
+ MSG = 'Repeated include of shared_examples %<name>s ' \
50
+ 'on line(s) %<repeat>s'
51
+
52
+ # @!method several_include_examples?(node)
53
+ def_node_matcher :several_include_examples?, <<~PATTERN
54
+ (begin <#include_examples? #include_examples? ...>)
55
+ PATTERN
56
+
57
+ # @!method include_examples?(node)
58
+ def_node_matcher :include_examples?,
59
+ '(send nil? #Includes.examples ...)'
60
+
61
+ # @!method shared_examples_name(node)
62
+ def_node_matcher :shared_examples_name,
63
+ '(send nil? #Includes.examples $_name ...)'
64
+
65
+ def on_begin(node)
66
+ return unless several_include_examples?(node)
67
+
68
+ repeated_include_examples(node).each do |item, repeats|
69
+ add_offense(item, message: message(item, repeats))
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def repeated_include_examples(node)
76
+ node
77
+ .children
78
+ .select { |child| literal_include_examples?(child) }
79
+ .group_by { |child| signature_keys(child) }
80
+ .values
81
+ .reject(&:one?)
82
+ .flat_map { |items| add_repeated_lines(items) }
83
+ end
84
+
85
+ def literal_include_examples?(node)
86
+ include_examples?(node) &&
87
+ node.arguments.all?(&:recursive_literal_or_const?)
88
+ end
89
+
90
+ def add_repeated_lines(items)
91
+ repeated_lines = items.map(&:first_line)
92
+ items.map { |item| [item, repeated_lines - [item.first_line]] }
93
+ end
94
+
95
+ def signature_keys(item)
96
+ item.arguments
97
+ end
98
+
99
+ def message(item, repeats)
100
+ format(MSG, name: shared_examples_name(item).source, repeat: repeats)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for repeated calls to subject missing that it is memoized.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it do
11
+ # subject
12
+ # expect { subject }.to not_change { A.count }
13
+ # end
14
+ #
15
+ # it do
16
+ # expect { subject }.to change { A.count }
17
+ # expect { subject }.to not_change { A.count }
18
+ # end
19
+ #
20
+ # # good
21
+ # it do
22
+ # expect { my_method }.to change { A.count }
23
+ # expect { my_method }.to not_change { A.count }
24
+ # end
25
+ #
26
+ # # also good
27
+ # it do
28
+ # expect { subject.a }.to change { A.count }
29
+ # expect { subject.b }.to not_change { A.count }
30
+ # end
31
+ #
32
+ class RepeatedSubjectCall < Base
33
+ include TopLevelGroup
34
+
35
+ MSG = 'Calls to subject are memoized, this block is misleading'
36
+
37
+ # @!method subject?(node)
38
+ # Find a named or unnamed subject definition
39
+ #
40
+ # @example anonymous subject
41
+ # subject?(parse('subject { foo }').ast) do |name|
42
+ # name # => :subject
43
+ # end
44
+ #
45
+ # @example named subject
46
+ # subject?(parse('subject(:thing) { foo }').ast) do |name|
47
+ # name # => :thing
48
+ # end
49
+ #
50
+ # @param node [RuboCop::AST::Node]
51
+ #
52
+ # @yield [Symbol] subject name
53
+ def_node_matcher :subject?, <<-PATTERN
54
+ (block
55
+ (send nil?
56
+ { #Subjects.all (sym $_) | $#Subjects.all }
57
+ ) args ...)
58
+ PATTERN
59
+
60
+ # @!method subject_calls(node, method_name)
61
+ def_node_search :subject_calls, <<~PATTERN
62
+ (send nil? %)
63
+ PATTERN
64
+
65
+ def on_top_level_group(node)
66
+ @subjects_by_node = detect_subjects_in_scope(node)
67
+
68
+ detect_offenses_in_block(node)
69
+ end
70
+
71
+ private
72
+
73
+ def detect_offense(subject_node)
74
+ return if subject_node.chained?
75
+ return if subject_node.parent.send_type?
76
+ return unless (block_node = expect_block(subject_node))
77
+
78
+ add_offense(block_node)
79
+ end
80
+
81
+ def expect_block(node)
82
+ node.each_ancestor(:block).find { |block| block.method?(:expect) }
83
+ end
84
+
85
+ def detect_offenses_in_block(node, subject_names = [])
86
+ subject_names = [*subject_names, *@subjects_by_node[node]]
87
+
88
+ if example?(node)
89
+ return detect_offenses_in_example(node, subject_names)
90
+ end
91
+
92
+ node.each_child_node(:send, :def, :block, :begin) do |child|
93
+ detect_offenses_in_block(child, subject_names)
94
+ end
95
+ end
96
+
97
+ def detect_offenses_in_example(node, subject_names)
98
+ return unless node.body
99
+
100
+ subjects_used = Hash.new(false)
101
+
102
+ subject_calls(node.body, Set[*subject_names, :subject]).each do |call|
103
+ if subjects_used[call.method_name]
104
+ detect_offense(call)
105
+ else
106
+ subjects_used[call.method_name] = true
107
+ end
108
+ end
109
+ end
110
+
111
+ def detect_subjects_in_scope(node)
112
+ node.each_descendant(:block).with_object({}) do |child, h|
113
+ subject?(child) do |name|
114
+ outer_example_group = child.each_ancestor(:block).find do |a|
115
+ example_group?(a)
116
+ end
117
+
118
+ (h[outer_example_group] ||= []) << name
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end