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,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for consistent style of stub's return setting.
7
+ #
8
+ # Enforces either `and_return` or block-style return in the cases
9
+ # where the returned value is constant. Ignores dynamic returned values
10
+ # are the result would be different
11
+ #
12
+ # This cop can be configured using the `EnforcedStyle` option
13
+ #
14
+ # @example `EnforcedStyle: and_return` (default)
15
+ # # bad
16
+ # allow(Foo).to receive(:bar) { "baz" }
17
+ # expect(Foo).to receive(:bar) { "baz" }
18
+ #
19
+ # # good
20
+ # allow(Foo).to receive(:bar).and_return("baz")
21
+ # expect(Foo).to receive(:bar).and_return("baz")
22
+ # # also good as the returned value is dynamic
23
+ # allow(Foo).to receive(:bar) { bar.baz }
24
+ #
25
+ # @example `EnforcedStyle: block`
26
+ # # bad
27
+ # allow(Foo).to receive(:bar).and_return("baz")
28
+ # expect(Foo).to receive(:bar).and_return("baz")
29
+ #
30
+ # # good
31
+ # allow(Foo).to receive(:bar) { "baz" }
32
+ # expect(Foo).to receive(:bar) { "baz" }
33
+ # # also good as the returned value is dynamic
34
+ # allow(Foo).to receive(:bar).and_return(bar.baz)
35
+ #
36
+ class ReturnFromStub < Base
37
+ extend AutoCorrector
38
+ include ConfigurableEnforcedStyle
39
+
40
+ MSG_AND_RETURN = 'Use `and_return` for static values.'
41
+ MSG_BLOCK = 'Use block for static values.'
42
+ RESTRICT_ON_SEND = %i[and_return].freeze
43
+
44
+ # @!method contains_stub?(node)
45
+ def_node_search :contains_stub?, '(send nil? :receive (...))'
46
+
47
+ # @!method stub_with_block?(node)
48
+ def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
49
+
50
+ # @!method and_return_value(node)
51
+ def_node_search :and_return_value, <<~PATTERN
52
+ $(send _ :and_return $(...))
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ return unless style == :block
57
+ return unless contains_stub?(node)
58
+
59
+ check_and_return_call(node)
60
+ end
61
+
62
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
63
+ return unless style == :and_return
64
+ return unless stub_with_block?(node)
65
+
66
+ check_block_body(node)
67
+ end
68
+
69
+ private
70
+
71
+ def check_and_return_call(node)
72
+ and_return_value(node) do |and_return, args|
73
+ unless dynamic?(args)
74
+ add_offense(and_return.loc.selector, message: MSG_BLOCK) do |corr|
75
+ AndReturnCallCorrector.new(and_return).call(corr)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def check_block_body(block)
82
+ body = block.body
83
+ unless dynamic?(body) # rubocop:disable Style/GuardClause
84
+ add_offense(block.loc.begin, message: MSG_AND_RETURN) do |corrector|
85
+ BlockBodyCorrector.new(block).call(corrector)
86
+ end
87
+ end
88
+ end
89
+
90
+ def dynamic?(node)
91
+ node && !node.recursive_literal_or_const?
92
+ end
93
+
94
+ # :nodoc:
95
+ class AndReturnCallCorrector
96
+ def initialize(node)
97
+ @node = node
98
+ @receiver = node.receiver
99
+ @arg = node.first_argument
100
+ end
101
+
102
+ def call(corrector)
103
+ # Heredoc autocorrection is not yet implemented.
104
+ return if heredoc?
105
+
106
+ corrector.replace(range, " { #{replacement} }")
107
+ end
108
+
109
+ private
110
+
111
+ attr_reader :node, :receiver, :arg
112
+
113
+ def heredoc?
114
+ arg.loc.is_a?(Parser::Source::Map::Heredoc)
115
+ end
116
+
117
+ def range
118
+ Parser::Source::Range.new(
119
+ node.source_range.source_buffer,
120
+ receiver.source_range.end_pos,
121
+ node.source_range.end_pos
122
+ )
123
+ end
124
+
125
+ def replacement
126
+ if hash_without_braces?
127
+ "{ #{arg.source} }"
128
+ else
129
+ arg.source
130
+ end
131
+ end
132
+
133
+ def hash_without_braces?
134
+ arg.hash_type? && !arg.braces?
135
+ end
136
+ end
137
+
138
+ # :nodoc:
139
+ class BlockBodyCorrector
140
+ def initialize(block)
141
+ @block = block
142
+ @node = block.parent
143
+ @body = block.body || NULL_BLOCK_BODY
144
+ end
145
+
146
+ def call(corrector)
147
+ # Heredoc autocorrection is not yet implemented.
148
+ return if heredoc?
149
+
150
+ corrector.replace(
151
+ block,
152
+ "#{block.send_node.source}.and_return(#{body.source})"
153
+ )
154
+ end
155
+
156
+ private
157
+
158
+ attr_reader :node, :block, :body
159
+
160
+ def heredoc?
161
+ body.loc.is_a?(Parser::Source::Map::Heredoc)
162
+ end
163
+
164
+ NULL_BLOCK_BODY = Struct.new(:loc, :source).new(nil, 'nil')
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for let scattered across the example group.
7
+ #
8
+ # Group lets together
9
+ #
10
+ # @example
11
+ # # bad
12
+ # describe Foo do
13
+ # let(:foo) { 1 }
14
+ # subject { Foo }
15
+ # let(:bar) { 2 }
16
+ # before { prepare }
17
+ # let!(:baz) { 3 }
18
+ # end
19
+ #
20
+ # # good
21
+ # describe Foo do
22
+ # subject { Foo }
23
+ # before { prepare }
24
+ # let(:foo) { 1 }
25
+ # let(:bar) { 2 }
26
+ # let!(:baz) { 3 }
27
+ # end
28
+ #
29
+ class ScatteredLet < Base
30
+ extend AutoCorrector
31
+
32
+ MSG = 'Group all let/let! blocks in the example group together.'
33
+
34
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
35
+ return unless example_group_with_body?(node)
36
+
37
+ check_let_declarations(node.body)
38
+ end
39
+
40
+ private
41
+
42
+ def check_let_declarations(body)
43
+ lets = body.each_child_node.select { |node| let?(node) }
44
+
45
+ first_let = lets.first
46
+ lets.each_with_index do |node, idx|
47
+ next if node.sibling_index == first_let.sibling_index + idx
48
+
49
+ add_offense(node) do |corrector|
50
+ RuboCop::RSpec::Corrector::MoveNode.new(
51
+ node, corrector, processed_source
52
+ ).move_after(first_let)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for setup scattered across multiple hooks in an example group.
7
+ #
8
+ # Unify `before`, `after`, and `around` hooks when possible.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # describe Foo do
13
+ # before { setup1 }
14
+ # before { setup2 }
15
+ # end
16
+ #
17
+ # # good
18
+ # describe Foo do
19
+ # before do
20
+ # setup1
21
+ # setup2
22
+ # end
23
+ # end
24
+ #
25
+ class ScatteredSetup < Base
26
+ include FinalEndLocation
27
+ include RangeHelp
28
+ extend AutoCorrector
29
+
30
+ MSG = 'Do not define multiple `%<hook_name>s` hooks in the same ' \
31
+ 'example group (also defined on %<lines>s).'
32
+
33
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
34
+ return unless example_group?(node)
35
+
36
+ repeated_hooks(node).each do |occurrences|
37
+ occurrences.each do |occurrence|
38
+ message = message(occurrences, occurrence)
39
+ add_offense(occurrence, message: message) do |corrector|
40
+ autocorrect(corrector, occurrences.first, occurrence)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def repeated_hooks(node)
49
+ hooks = RuboCop::RSpec::ExampleGroup.new(node)
50
+ .hooks
51
+ .select(&:knowable_scope?)
52
+ .group_by { |hook| [hook.name, hook.scope, hook.metadata] }
53
+ .values
54
+ .reject(&:one?)
55
+
56
+ hooks.map do |hook|
57
+ hook.map(&:to_node)
58
+ end
59
+ end
60
+
61
+ def lines_msg(numbers)
62
+ if numbers.size == 1
63
+ "line #{numbers.first}"
64
+ else
65
+ "lines #{numbers.join(', ')}"
66
+ end
67
+ end
68
+
69
+ def message(occurrences, occurrence)
70
+ lines = occurrences.map(&:first_line)
71
+ lines_except_current = lines - [occurrence.first_line]
72
+ format(MSG, hook_name: occurrences.first.method_name,
73
+ lines: lines_msg(lines_except_current))
74
+ end
75
+
76
+ def autocorrect(corrector, first_occurrence, occurrence)
77
+ return if first_occurrence == occurrence || !first_occurrence.body
78
+
79
+ # Take heredocs into account
80
+ body = occurrence.body&.source_range&.with(
81
+ end_pos: final_end_location(occurrence).begin_pos
82
+ )
83
+
84
+ corrector.insert_after(first_occurrence.body,
85
+ "\n#{body&.source}")
86
+ corrector.remove(range_by_whole_lines(occurrence.source_range,
87
+ include_final_newline: true))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for proper shared_context and shared_examples usage.
7
+ #
8
+ # If there are no examples defined, use shared_context.
9
+ # If there is no setup defined, use shared_examples.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # RSpec.shared_context 'only examples here' do
14
+ # it 'does x' do
15
+ # end
16
+ #
17
+ # it 'does y' do
18
+ # end
19
+ # end
20
+ #
21
+ # # good
22
+ # RSpec.shared_examples 'only examples here' do
23
+ # it 'does x' do
24
+ # end
25
+ #
26
+ # it 'does y' do
27
+ # end
28
+ # end
29
+ #
30
+ # @example
31
+ # # bad
32
+ # RSpec.shared_examples 'only setup here' do
33
+ # subject(:foo) { :bar }
34
+ #
35
+ # let(:baz) { :bazz }
36
+ #
37
+ # before do
38
+ # something
39
+ # end
40
+ # end
41
+ #
42
+ # # good
43
+ # RSpec.shared_context 'only setup here' do
44
+ # subject(:foo) { :bar }
45
+ #
46
+ # let(:baz) { :bazz }
47
+ #
48
+ # before do
49
+ # something
50
+ # end
51
+ # end
52
+ #
53
+ class SharedContext < Base
54
+ extend AutoCorrector
55
+
56
+ MSG_EXAMPLES = "Use `shared_examples` when you don't define context."
57
+ MSG_CONTEXT = "Use `shared_context` when you don't define examples."
58
+
59
+ # @!method examples?(node)
60
+ def_node_search :examples?, <<~PATTERN
61
+ (send nil? {#Includes.examples #Examples.all} ...)
62
+ PATTERN
63
+
64
+ # @!method context?(node)
65
+ def_node_search :context?, <<~PATTERN
66
+ (send nil?
67
+ {#Subjects.all #Helpers.all #Includes.context #Hooks.all} ...
68
+ )
69
+ PATTERN
70
+
71
+ # @!method shared_context(node)
72
+ def_node_matcher :shared_context, <<~PATTERN
73
+ (block (send #rspec? #SharedGroups.context ...) ...)
74
+ PATTERN
75
+
76
+ # @!method shared_example(node)
77
+ def_node_matcher :shared_example, <<~PATTERN
78
+ (block (send #rspec? #SharedGroups.examples ...) ...)
79
+ PATTERN
80
+
81
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
82
+ context_with_only_examples(node) do
83
+ add_offense(node.send_node, message: MSG_EXAMPLES) do |corrector|
84
+ corrector.replace(node.send_node.loc.selector, 'shared_examples')
85
+ end
86
+ end
87
+
88
+ examples_with_only_context(node) do
89
+ add_offense(node.send_node, message: MSG_CONTEXT) do |corrector|
90
+ corrector.replace(node.send_node.loc.selector, 'shared_context')
91
+ end
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def context_with_only_examples(node)
98
+ shared_context(node) { yield if examples?(node) && !context?(node) }
99
+ end
100
+
101
+ def examples_with_only_context(node)
102
+ shared_example(node) { yield if context?(node) && !examples?(node) }
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for consistent style for shared example names.
7
+ #
8
+ # Enforces either `string` or `symbol` for shared example names.
9
+ #
10
+ # This cop can be configured using the `EnforcedStyle` option
11
+ #
12
+ # @example `EnforcedStyle: string` (default)
13
+ # # bad
14
+ # it_behaves_like :foo_bar_baz
15
+ # it_should_behave_like :foo_bar_baz
16
+ # shared_examples :foo_bar_baz
17
+ # shared_examples_for :foo_bar_baz
18
+ # include_examples :foo_bar_baz
19
+ #
20
+ # # good
21
+ # it_behaves_like 'foo bar baz'
22
+ # it_should_behave_like 'foo bar baz'
23
+ # shared_examples 'foo bar baz'
24
+ # shared_examples_for 'foo bar baz'
25
+ # include_examples 'foo bar baz'
26
+ #
27
+ # @example `EnforcedStyle: symbol`
28
+ # # bad
29
+ # it_behaves_like 'foo bar baz'
30
+ # it_should_behave_like 'foo bar baz'
31
+ # shared_examples 'foo bar baz'
32
+ # shared_examples_for 'foo bar baz'
33
+ # include_examples 'foo bar baz'
34
+ #
35
+ # # good
36
+ # it_behaves_like :foo_bar_baz
37
+ # it_should_behave_like :foo_bar_baz
38
+ # shared_examples :foo_bar_baz
39
+ # shared_examples_for :foo_bar_baz
40
+ # include_examples :foo_bar_baz
41
+ #
42
+ class SharedExamples < Base
43
+ extend AutoCorrector
44
+ include ConfigurableEnforcedStyle
45
+
46
+ # @!method shared_examples(node)
47
+ def_node_matcher :shared_examples, <<~PATTERN
48
+ {
49
+ (send #rspec? #SharedGroups.all $_ ...)
50
+ (send nil? #Includes.all $_ ...)
51
+ }
52
+ PATTERN
53
+
54
+ def on_send(node)
55
+ shared_examples(node) do |ast_node|
56
+ next unless offense?(ast_node)
57
+
58
+ checker = new_checker(ast_node)
59
+ add_offense(ast_node, message: checker.message) do |corrector|
60
+ corrector.replace(ast_node, checker.preferred_style)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def offense?(ast_node)
68
+ if style == :symbol
69
+ ast_node.str_type?
70
+ else # string
71
+ ast_node.sym_type?
72
+ end
73
+ end
74
+
75
+ def new_checker(ast_node)
76
+ if style == :symbol
77
+ SymbolChecker.new(ast_node)
78
+ else # string
79
+ StringChecker.new(ast_node)
80
+ end
81
+ end
82
+
83
+ # :nodoc:
84
+ class SymbolChecker
85
+ MSG = 'Prefer %<prefer>s over `%<current>s` ' \
86
+ 'to symbolize shared examples.'
87
+
88
+ attr_reader :node
89
+
90
+ def initialize(node)
91
+ @node = node
92
+ end
93
+
94
+ def message
95
+ format(MSG, prefer: preferred_style, current: node.value.inspect)
96
+ end
97
+
98
+ def preferred_style
99
+ ":#{node.value.to_s.downcase.tr(' ', '_')}"
100
+ end
101
+ end
102
+
103
+ # :nodoc:
104
+ class StringChecker
105
+ MSG = 'Prefer %<prefer>s over `%<current>s` ' \
106
+ 'to titleize shared examples.'
107
+
108
+ attr_reader :node
109
+
110
+ def initialize(node)
111
+ @node = node
112
+ end
113
+
114
+ def message
115
+ format(MSG, prefer: preferred_style, current: node.value.inspect)
116
+ end
117
+
118
+ def preferred_style
119
+ "'#{node.value.to_s.tr('_', ' ')}'"
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that chains of messages contain more than one element.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # allow(foo).to receive_message_chain(:bar).and_return(42)
11
+ #
12
+ # # good
13
+ # allow(foo).to receive(:bar).and_return(42)
14
+ #
15
+ # # also good
16
+ # allow(foo).to receive(:bar, :baz)
17
+ # allow(foo).to receive("bar.baz")
18
+ #
19
+ class SingleArgumentMessageChain < Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Use `%<recommended>s` instead of calling ' \
23
+ '`%<called>s` with a single argument.'
24
+ RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
25
+
26
+ # @!method message_chain(node)
27
+ def_node_matcher :message_chain, <<~PATTERN
28
+ (send _ {:receive_message_chain :stub_chain} $_)
29
+ PATTERN
30
+
31
+ # @!method single_key_hash?(node)
32
+ def_node_matcher :single_key_hash?, '(hash pair)'
33
+
34
+ def on_send(node)
35
+ message_chain(node) do |arg|
36
+ return if valid_usage?(arg)
37
+
38
+ method = node.method_name
39
+ msg = format(MSG, recommended: replacement(method), called: method)
40
+
41
+ add_offense(node.loc.selector, message: msg) do |corrector|
42
+ autocorrect(corrector, node, method, arg)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def autocorrect(corrector, node, method, arg)
50
+ corrector.replace(node.loc.selector, replacement(method))
51
+ autocorrect_hash_arg(corrector, arg) if single_key_hash?(arg)
52
+ autocorrect_array_arg(corrector, arg) if arg.array_type?
53
+ end
54
+
55
+ def valid_usage?(node)
56
+ return true unless node.literal? || node.array_type?
57
+
58
+ case node.type
59
+ when :hash then !single_key_hash?(node)
60
+ when :array then !single_element_array?(node)
61
+ else node.to_s.include?('.')
62
+ end
63
+ end
64
+
65
+ def single_element_array?(node)
66
+ node.child_nodes.one?
67
+ end
68
+
69
+ def autocorrect_hash_arg(corrector, arg)
70
+ key, value = *arg.children.first
71
+
72
+ corrector.replace(arg, key_to_arg(key))
73
+ corrector.insert_after(arg.parent.loc.end,
74
+ ".and_return(#{value.source})")
75
+ end
76
+
77
+ def autocorrect_array_arg(corrector, arg)
78
+ value = arg.children.first
79
+
80
+ corrector.replace(arg, value.source)
81
+ end
82
+
83
+ def key_to_arg(node)
84
+ node.sym_type? ? ":#{node.value}" : node.source
85
+ end
86
+
87
+ def replacement(method)
88
+ method.equal?(:receive_message_chain) ? 'receive' : 'stub'
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for passing a block to `skip` within examples.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # it 'does something' do
11
+ # skip 'not yet implemented' do
12
+ # do_something
13
+ # end
14
+ # end
15
+ #
16
+ # # good
17
+ # it 'does something' do
18
+ # skip 'not yet implemented'
19
+ # do_something
20
+ # end
21
+ #
22
+ # # good - when outside example
23
+ # skip 'not yet implemented' do
24
+ # end
25
+ #
26
+ class SkipBlockInsideExample < Base
27
+ MSG = "Don't pass a block to `skip` inside examples."
28
+
29
+ def on_block(node)
30
+ return unless node.method?(:skip)
31
+ return unless inside_example?(node)
32
+
33
+ add_offense(node)
34
+ end
35
+
36
+ alias on_numblock on_block
37
+
38
+ private
39
+
40
+ def inside_example?(node)
41
+ node.each_ancestor(:block).any? { |ancestor| example?(ancestor) }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end