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,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Prefer negated matchers over `to change.by(0)`.
7
+ #
8
+ # In the case of composite expectations, cop suggest using the
9
+ # negation matchers of `RSpec::Matchers#change`.
10
+ #
11
+ # By default the cop does not support autocorrect of
12
+ # compound expectations, but if you set the
13
+ # negated matcher for `change`, e.g. `not_change` with
14
+ # the `NegatedMatcher` option, the cop will perform the autocorrection.
15
+ #
16
+ # @example NegatedMatcher: ~ (default)
17
+ # # bad
18
+ # expect { run }.to change(Foo, :bar).by(0)
19
+ # expect { run }.to change { Foo.bar }.by(0)
20
+ #
21
+ # # bad - compound expectations (does not support autocorrection)
22
+ # expect { run }
23
+ # .to change(Foo, :bar).by(0)
24
+ # .and change(Foo, :baz).by(0)
25
+ # expect { run }
26
+ # .to change { Foo.bar }.by(0)
27
+ # .and change { Foo.baz }.by(0)
28
+ #
29
+ # # good
30
+ # expect { run }.not_to change(Foo, :bar)
31
+ # expect { run }.not_to change { Foo.bar }
32
+ #
33
+ # # good - compound expectations
34
+ # define_negated_matcher :not_change, :change
35
+ # expect { run }
36
+ # .to not_change(Foo, :bar)
37
+ # .and not_change(Foo, :baz)
38
+ # expect { run }
39
+ # .to not_change { Foo.bar }
40
+ # .and not_change { Foo.baz }
41
+ #
42
+ # @example NegatedMatcher: not_change
43
+ # # bad (support autocorrection to good case)
44
+ # expect { run }
45
+ # .to change(Foo, :bar).by(0)
46
+ # .and change(Foo, :baz).by(0)
47
+ # expect { run }
48
+ # .to change { Foo.bar }.by(0)
49
+ # .and change { Foo.baz }.by(0)
50
+ #
51
+ # # good
52
+ # define_negated_matcher :not_change, :change
53
+ # expect { run }
54
+ # .to not_change(Foo, :bar)
55
+ # .and not_change(Foo, :baz)
56
+ # expect { run }
57
+ # .to not_change { Foo.bar }
58
+ # .and not_change { Foo.baz }
59
+ #
60
+ class ChangeByZero < Base
61
+ extend AutoCorrector
62
+ include RangeHelp
63
+
64
+ MSG = 'Prefer `not_to change` over `to %<method>s.by(0)`.'
65
+ MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
66
+ 'over `%<method>s.by(0)`.'
67
+ CHANGE_METHODS = Set[:change, :a_block_changing, :changing].freeze
68
+ RESTRICT_ON_SEND = CHANGE_METHODS.freeze
69
+
70
+ # @!method expect_change_with_arguments(node)
71
+ def_node_matcher :expect_change_with_arguments, <<~PATTERN
72
+ (send
73
+ $(send nil? CHANGE_METHODS ...) :by
74
+ (int 0))
75
+ PATTERN
76
+
77
+ # @!method expect_change_with_block(node)
78
+ def_node_matcher :expect_change_with_block, <<~PATTERN
79
+ (send
80
+ (block
81
+ $(send nil? CHANGE_METHODS)
82
+ (args)
83
+ (send (...) _)) :by
84
+ (int 0))
85
+ PATTERN
86
+
87
+ # @!method change_nodes(node)
88
+ def_node_search :change_nodes, <<~PATTERN
89
+ $(send nil? CHANGE_METHODS ...)
90
+ PATTERN
91
+
92
+ def on_send(node)
93
+ expect_change_with_arguments(node.parent) do |change|
94
+ register_offense(node.parent, change)
95
+ end
96
+
97
+ expect_change_with_block(node.parent.parent) do |change|
98
+ register_offense(node.parent.parent, change)
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ # rubocop:disable Metrics/MethodLength
105
+ def register_offense(node, change_node)
106
+ if compound_expectations?(node)
107
+ add_offense(node.source_range,
108
+ message: message_compound(change_node)) do |corrector|
109
+ autocorrect_compound(corrector, node)
110
+ end
111
+ else
112
+ add_offense(node.source_range,
113
+ message: message(change_node)) do |corrector|
114
+ autocorrect(corrector, node, change_node)
115
+ end
116
+ end
117
+ end
118
+ # rubocop:enable Metrics/MethodLength
119
+
120
+ def compound_expectations?(node)
121
+ %i[and or & |].include?(node.parent.method_name)
122
+ end
123
+
124
+ def message(change_node)
125
+ format(MSG, method: change_node.method_name)
126
+ end
127
+
128
+ def message_compound(change_node)
129
+ format(MSG_COMPOUND, preferred: preferred_method,
130
+ method: change_node.method_name)
131
+ end
132
+
133
+ def autocorrect(corrector, node, change_node)
134
+ corrector.replace(node.parent.loc.selector, 'not_to')
135
+ corrector.replace(change_node.loc.selector, 'change')
136
+ range = node.loc.dot.with(end_pos: node.source_range.end_pos)
137
+ corrector.remove(range)
138
+ end
139
+
140
+ def autocorrect_compound(corrector, node)
141
+ return unless negated_matcher
142
+
143
+ change_nodes(node) do |change_node|
144
+ corrector.replace(change_node.loc.selector, negated_matcher)
145
+ insert_operator(corrector, node, change_node)
146
+ remove_by_zero(corrector, node, change_node)
147
+ end
148
+ end
149
+
150
+ def insert_operator(corrector, node, change_node)
151
+ operator = node.right_siblings.first
152
+ return unless %i[& |].include?(operator)
153
+
154
+ corrector.insert_after(
155
+ replace_node(node, change_node), " #{operator}"
156
+ )
157
+ end
158
+
159
+ def replace_node(node, change_node)
160
+ expect_change_with_arguments(node) ? change_node : change_node.parent
161
+ end
162
+
163
+ def remove_by_zero(corrector, node, change_node)
164
+ range = node.loc.dot.with(end_pos: node.source_range.end_pos)
165
+ if change_node.loc.line == range.line
166
+ corrector.remove(range)
167
+ else
168
+ corrector.remove(
169
+ range_by_whole_lines(range, include_final_newline: true)
170
+ )
171
+ end
172
+ end
173
+
174
+ def negated_matcher
175
+ cop_config['NegatedMatcher']
176
+ end
177
+
178
+ def preferred_method
179
+ negated_matcher ? "`#{negated_matcher}`" : 'negated matchers'
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Enforces consistent use of `be_a` or `be_kind_of`.
7
+ #
8
+ # @example EnforcedStyle: be_a (default)
9
+ # # bad
10
+ # expect(object).to be_kind_of(String)
11
+ # expect(object).to be_a_kind_of(String)
12
+ #
13
+ # # good
14
+ # expect(object).to be_a(String)
15
+ # expect(object).to be_an(String)
16
+ #
17
+ # @example EnforcedStyle: be_kind_of
18
+ # # bad
19
+ # expect(object).to be_a(String)
20
+ # expect(object).to be_an(String)
21
+ #
22
+ # # good
23
+ # expect(object).to be_kind_of(String)
24
+ # expect(object).to be_a_kind_of(String)
25
+ #
26
+ class ClassCheck < Base
27
+ extend AutoCorrector
28
+ include ConfigurableEnforcedStyle
29
+
30
+ MSG = 'Prefer `%<preferred>s` over `%<current>s`.'
31
+
32
+ METHOD_NAMES_FOR_BE_A = ::Set[
33
+ :be_a,
34
+ :be_an
35
+ ].freeze
36
+
37
+ METHOD_NAMES_FOR_KIND_OF = ::Set[
38
+ :be_a_kind_of,
39
+ :be_kind_of
40
+ ].freeze
41
+
42
+ PREFERRED_METHOD_NAME_BY_STYLE = {
43
+ be_a: :be_a,
44
+ be_kind_of: :be_kind_of
45
+ }.freeze
46
+
47
+ RESTRICT_ON_SEND = %i[
48
+ be_a
49
+ be_a_kind_of
50
+ be_an
51
+ be_kind_of
52
+ ].freeze
53
+
54
+ def on_send(node)
55
+ return unless offending?(node)
56
+
57
+ add_offense(
58
+ node.loc.selector,
59
+ message: format_message(node)
60
+ ) do |corrector|
61
+ autocorrect(corrector, node)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def autocorrect(corrector, node)
68
+ corrector.replace(node.loc.selector, preferred_method_name)
69
+ end
70
+
71
+ def format_message(node)
72
+ format(
73
+ MSG,
74
+ current: node.method_name,
75
+ preferred: preferred_method_name
76
+ )
77
+ end
78
+
79
+ def offending?(node)
80
+ !node.receiver && !preferred_method_name?(node.method_name)
81
+ end
82
+
83
+ def preferred_method_name?(method_name)
84
+ preferred_method_names.include?(method_name)
85
+ end
86
+
87
+ def preferred_method_name
88
+ PREFERRED_METHOD_NAME_BY_STYLE[style]
89
+ end
90
+
91
+ def preferred_method_names
92
+ if style == :be_a
93
+ METHOD_NAMES_FOR_BE_A
94
+ else
95
+ METHOD_NAMES_FOR_KIND_OF
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks where `contain_exactly` is used.
7
+ #
8
+ # This cop checks for the following:
9
+ # - Prefer `match_array` when matching array values.
10
+ # - Prefer `be_empty` when using `contain_exactly` with no arguments.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # it { is_expected.to contain_exactly(*array1, *array2) }
15
+ #
16
+ # # good
17
+ # it { is_expected.to match_array(array1 + array2) }
18
+ #
19
+ # # good
20
+ # it { is_expected.to contain_exactly(content, *array) }
21
+ #
22
+ class ContainExactly < Base
23
+ extend AutoCorrector
24
+
25
+ MSG = 'Prefer `match_array` when matching array values.'
26
+ RESTRICT_ON_SEND = %i[contain_exactly].freeze
27
+
28
+ def on_send(node)
29
+ return if node.arguments.empty?
30
+
31
+ check_populated_collection(node)
32
+ end
33
+
34
+ private
35
+
36
+ def check_populated_collection(node)
37
+ return unless node.each_child_node.all?(&:splat_type?)
38
+
39
+ add_offense(node) do |corrector|
40
+ autocorrect_for_populated_array(node, corrector)
41
+ end
42
+ end
43
+
44
+ def autocorrect_for_populated_array(node, corrector)
45
+ arrays = node.arguments.map do |splat_node|
46
+ splat_node.children.first
47
+ end
48
+ corrector.replace(
49
+ node,
50
+ "match_array(#{arrays.map(&:source).join(' + ')})"
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # `context` should not be used for specifying methods.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # context '#foo_bar' do
11
+ # # ...
12
+ # end
13
+ #
14
+ # context '.foo_bar' do
15
+ # # ...
16
+ # end
17
+ #
18
+ # # good
19
+ # describe '#foo_bar' do
20
+ # # ...
21
+ # end
22
+ #
23
+ # describe '.foo_bar' do
24
+ # # ...
25
+ # end
26
+ #
27
+ class ContextMethod < Base
28
+ extend AutoCorrector
29
+
30
+ MSG = 'Use `describe` for testing methods.'
31
+
32
+ # @!method context_method(node)
33
+ def_node_matcher :context_method, <<~PATTERN
34
+ (block
35
+ (send #rspec? :context
36
+ ${(str #method_name?) (dstr (str #method_name?) ...)}
37
+ ...)
38
+ ...)
39
+ PATTERN
40
+
41
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
42
+ context_method(node) do |context|
43
+ add_offense(context) do |corrector|
44
+ corrector.replace(node.send_node.loc.selector, 'describe')
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def method_name?(description)
52
+ description.start_with?('.', '#')
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that `context` docstring starts with an allowed prefix.
7
+ #
8
+ # The default list of prefixes is minimal. Users are encouraged to tailor
9
+ # the configuration to meet project needs. Other acceptable prefixes may
10
+ # include `if`, `unless`, `for`, `before`, `after`, or `during`.
11
+ # They may consist of multiple words if desired.
12
+ #
13
+ # @see http://www.betterspecs.org/#contexts
14
+ #
15
+ # @example `Prefixes` configuration
16
+ # # .rubocop.yml
17
+ # # RSpec/ContextWording:
18
+ # # Prefixes:
19
+ # # - when
20
+ # # - with
21
+ # # - without
22
+ # # - if
23
+ # # - unless
24
+ # # - for
25
+ #
26
+ # @example
27
+ # # bad
28
+ # context 'the display name not present' do
29
+ # # ...
30
+ # end
31
+ #
32
+ # # good
33
+ # context 'when the display name is not present' do
34
+ # # ...
35
+ # end
36
+ #
37
+ # This cop can be customized allowed context description pattern
38
+ # with `AllowedPatterns`. By default, there are no checking by pattern.
39
+ #
40
+ # @example `AllowedPatterns` configuration
41
+ #
42
+ # # .rubocop.yml
43
+ # # RSpec/ContextWording:
44
+ # # AllowedPatterns:
45
+ # # - とき$
46
+ #
47
+ # @example
48
+ # # bad
49
+ # context '条件を満たす' do
50
+ # # ...
51
+ # end
52
+ #
53
+ # # good
54
+ # context '条件を満たすとき' do
55
+ # # ...
56
+ # end
57
+ #
58
+ class ContextWording < Base
59
+ include AllowedPattern
60
+
61
+ MSG = 'Context description should match %<patterns>s.'
62
+
63
+ # @!method context_wording(node)
64
+ def_node_matcher :context_wording, <<~PATTERN
65
+ (block (send #rspec? { :context :shared_context } $({str dstr xstr} ...) ...) ...)
66
+ PATTERN
67
+
68
+ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
69
+ context_wording(node) do |context|
70
+ if bad_pattern?(context)
71
+ message = format(MSG, patterns: expect_patterns)
72
+ add_offense(context, message: message)
73
+ end
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def allowed_patterns
80
+ super + prefix_regexes
81
+ end
82
+
83
+ def prefix_regexes
84
+ @prefix_regexes ||= prefixes.map { |pre| /^#{Regexp.escape(pre)}\b/ }
85
+ end
86
+
87
+ def bad_pattern?(node)
88
+ return false if allowed_patterns.empty?
89
+
90
+ !matches_allowed_pattern?(description(node))
91
+ end
92
+
93
+ def description(context)
94
+ if context.xstr_type?
95
+ context.value.value
96
+ else
97
+ context.value
98
+ end
99
+ end
100
+
101
+ def expect_patterns
102
+ inspected = allowed_patterns.map do |pattern|
103
+ pattern.inspect.gsub(/\A"|"\z/, '/')
104
+ end
105
+ return inspected.first if inspected.size == 1
106
+
107
+ inspected << "or #{inspected.pop}"
108
+ inspected.join(', ')
109
+ end
110
+
111
+ def prefixes
112
+ Array(cop_config.fetch('Prefixes', []))
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -3,7 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Check that the first argument to the top level describe is a constant.
6
+ # Check that the first argument to the top-level describe is a constant.
7
+ #
8
+ # It can be configured to ignore strings when certain metadata is passed.
9
+ #
10
+ # Ignores Rails and Aruba `type` metadata by default.
11
+ #
12
+ # @example `IgnoredMetadata` configuration
13
+ # # .rubocop.yml
14
+ # # RSpec/DescribeClass:
15
+ # # IgnoredMetadata:
16
+ # # type:
17
+ # # - request
18
+ # # - controller
7
19
  #
8
20
  # @example
9
21
  # # bad
@@ -12,41 +24,60 @@ module RuboCop
12
24
  #
13
25
  # # good
14
26
  # describe TestedClass do
27
+ # subject { described_class }
28
+ # end
29
+ #
30
+ # describe 'TestedClass::VERSION' do
31
+ # subject { Object.const_get(self.class.description) }
15
32
  # end
16
33
  #
17
34
  # describe "A feature example", type: :feature do
18
35
  # end
19
- class DescribeClass < Cop
20
- include RuboCop::RSpec::SpecOnly, RuboCop::RSpec::TopLevelDescribe
36
+ #
37
+ class DescribeClass < Base
38
+ include TopLevelGroup
21
39
 
22
- MSG = 'The first argument to describe should be '\
23
- 'the class or module being tested.'.freeze
40
+ MSG = 'The first argument to describe should be ' \
41
+ 'the class or module being tested.'
24
42
 
25
- def_node_matcher :valid_describe?, <<-PATTERN
26
- {(send {(const nil :RSpec) nil} :describe const ...) (send nil :describe)}
43
+ # @!method example_group_with_ignored_metadata?(node)
44
+ def_node_matcher :example_group_with_ignored_metadata?, <<~PATTERN
45
+ (send #rspec? :describe ... (hash <#ignored_metadata? ...>))
27
46
  PATTERN
28
47
 
29
- def_node_matcher :describe_with_metadata, <<-PATTERN
30
- (send {(const nil :RSpec) nil} :describe
31
- !const
32
- ...
33
- (hash $...))
48
+ # @!method not_a_const_described(node)
49
+ def_node_matcher :not_a_const_described, <<~PATTERN
50
+ (send #rspec? :describe $[!const !#string_constant?] ...)
34
51
  PATTERN
35
52
 
36
- def_node_matcher :rails_metadata?, <<-PATTERN
37
- (pair
38
- (sym :type)
39
- (sym {:request :feature :routing :view}))
53
+ # @!method sym_pair(node)
54
+ def_node_matcher :sym_pair, <<~PATTERN
55
+ (pair $sym $sym)
40
56
  PATTERN
41
57
 
42
- def on_top_level_describe(node, args)
43
- return if valid_describe?(node)
58
+ def on_top_level_group(node)
59
+ return if example_group_with_ignored_metadata?(node.send_node)
44
60
 
45
- describe_with_metadata(node) do |pairs|
46
- return if pairs.any?(&method(:rails_metadata?))
61
+ not_a_const_described(node.send_node) do |described|
62
+ add_offense(described)
47
63
  end
64
+ end
65
+
66
+ private
67
+
68
+ def ignored_metadata?(node)
69
+ sym_pair(node) do |key, value|
70
+ ignored_metadata[key.value.to_s].to_a.include?(value.value.to_s)
71
+ end
72
+ end
73
+
74
+ def string_constant?(described)
75
+ described.str_type? &&
76
+ described.value.match?(/^(?:(?:::)?[A-Z]\w*)+$/)
77
+ end
48
78
 
49
- add_offense(args.first, :expression)
79
+ def ignored_metadata
80
+ cop_config['IgnoredMetadata'] || {}
50
81
  end
51
82
  end
52
83
  end
@@ -16,20 +16,35 @@ module RuboCop
16
16
  #
17
17
  # describe MyClass, '.my_class_method' do
18
18
  # end
19
- class DescribeMethod < Cop
20
- include RuboCop::RSpec::SpecOnly,
21
- RuboCop::RSpec::TopLevelDescribe,
22
- RuboCop::RSpec::Util
19
+ #
20
+ class DescribeMethod < Base
21
+ include TopLevelGroup
22
+
23
+ MSG = 'The second argument to describe should be the method ' \
24
+ "being tested. '#instance' or '.class'."
25
+
26
+ # @!method second_string_literal_argument(node)
27
+ def_node_matcher :second_string_literal_argument, <<~PATTERN
28
+ (block
29
+ (send #rspec? :describe _first_argument ${str dstr} ...)
30
+ ...)
31
+ PATTERN
23
32
 
24
- MESSAGE = 'The second argument to describe should be the method ' \
25
- "being tested. '#instance' or '.class'".freeze
26
- METHOD_STRING_MATCHER = /^[\#\.].+/
33
+ # @!method method_name?(node)
34
+ def_node_matcher :method_name?, <<~PATTERN
35
+ {(str #method_name_prefix?) (dstr (str #method_name_prefix?) ...)}
36
+ PATTERN
37
+
38
+ def on_top_level_group(node)
39
+ second_string_literal_argument(node) do |argument|
40
+ add_offense(argument) unless method_name?(argument)
41
+ end
42
+ end
27
43
 
28
- def on_top_level_describe(_node, (_, second_arg))
29
- return unless second_arg && second_arg.type.equal?(:str)
30
- return if METHOD_STRING_MATCHER =~ one(second_arg.children)
44
+ private
31
45
 
32
- add_offense(second_arg, :expression, MESSAGE)
46
+ def method_name_prefix?(description)
47
+ description.start_with?('.', '#')
33
48
  end
34
49
  end
35
50
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Avoid describing symbols.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe :my_method do
11
+ # # ...
12
+ # end
13
+ #
14
+ # # good
15
+ # describe '#my_method' do
16
+ # # ...
17
+ # end
18
+ #
19
+ # @see https://github.com/rspec/rspec-core/issues/1610
20
+ class DescribeSymbol < Base
21
+ MSG = 'Avoid describing symbols.'
22
+ RESTRICT_ON_SEND = %i[describe].freeze
23
+
24
+ # @!method describe_symbol?(node)
25
+ def_node_matcher :describe_symbol?, <<~PATTERN
26
+ (send #rspec? :describe $sym ...)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ describe_symbol?(node) do |match|
31
+ add_offense(match)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end